diff --git a/server/agent/bot_builder.py b/server/agent/bot_builder.py index 32ccacd5..c0c5fdc8 100644 --- a/server/agent/bot_builder.py +++ b/server/agent/bot_builder.py @@ -3,8 +3,8 @@ from petercat_utils.data_class import ChatData from agent.base import AgentBuilder -from prompts.bot_builder import generate_prompt_by_user_id -from tools import bot_builder +from agent.prompts.bot_builder import generate_prompt_by_user_id +from agent.tools import bot_builder TOOL_MAPPING = { diff --git a/server/prompts/bot_builder.py b/server/agent/prompts/bot_builder.py similarity index 100% rename from server/prompts/bot_builder.py rename to server/agent/prompts/bot_builder.py diff --git a/server/prompts/bot_template.py b/server/agent/prompts/bot_template.py similarity index 100% rename from server/prompts/bot_template.py rename to server/agent/prompts/bot_template.py diff --git a/server/prompts/repo_pr.py b/server/agent/prompts/repo_pr.py similarity index 100% rename from server/prompts/repo_pr.py rename to server/agent/prompts/repo_pr.py diff --git a/server/agent/qa_chat.py b/server/agent/qa_chat.py index d0d47d69..bff0c681 100644 --- a/server/agent/qa_chat.py +++ b/server/agent/qa_chat.py @@ -1,12 +1,12 @@ from typing import AsyncIterator, Optional from agent.base import AgentBuilder from agent.llm import get_llm -from dao.botDAO import BotDAO -from models.bot import Bot -from prompts.bot_template import generate_prompt_by_repo_name +from core.dao.botDAO import BotDAO +from core.models.bot import Bot +from agent.prompts.bot_template import generate_prompt_by_repo_name from petercat_utils.data_class import ChatData -from tools import issue, sourcecode, knowledge, git_info +from agent.tools import issue, sourcecode, knowledge, git_info def get_tools(bot: Bot, token: Optional[str]): diff --git a/server/tools/bot_builder.py b/server/agent/tools/bot_builder.py similarity index 100% rename from server/tools/bot_builder.py rename to server/agent/tools/bot_builder.py diff --git a/server/tools/git_info.py b/server/agent/tools/git_info.py similarity index 100% rename from server/tools/git_info.py rename to server/agent/tools/git_info.py diff --git a/server/tools/helper.py b/server/agent/tools/helper.py similarity index 100% rename from server/tools/helper.py rename to server/agent/tools/helper.py diff --git a/server/tools/issue.py b/server/agent/tools/issue.py similarity index 98% rename from server/tools/issue.py rename to server/agent/tools/issue.py index 07a2411c..0111f4cb 100644 --- a/server/tools/issue.py +++ b/server/agent/tools/issue.py @@ -3,7 +3,7 @@ from github import Auth, Github from langchain.tools import tool -from tools.helper import need_github_login +from agent.tools.helper import need_github_login DEFAULT_REPO_NAME = "ant-design/ant-design" diff --git a/server/tools/knowledge.py b/server/agent/tools/knowledge.py similarity index 100% rename from server/tools/knowledge.py rename to server/agent/tools/knowledge.py diff --git a/server/tools/sourcecode.py b/server/agent/tools/sourcecode.py similarity index 100% rename from server/tools/sourcecode.py rename to server/agent/tools/sourcecode.py diff --git a/server/verify/rate_limit.py b/server/auth/rate_limit.py similarity index 100% rename from server/verify/rate_limit.py rename to server/auth/rate_limit.py diff --git a/server/routers/auth.py b/server/auth/router.py similarity index 100% rename from server/routers/auth.py rename to server/auth/router.py diff --git a/server/bot/builder.py b/server/bot/builder.py index 35603ac8..a1884508 100644 --- a/server/bot/builder.py +++ b/server/bot/builder.py @@ -5,7 +5,7 @@ from petercat_utils.data_class import RAGGitDocConfig from petercat_utils import git_doc_task -from prompts.bot_template import generate_prompt_by_repo_name +from agent.prompts.bot_template import generate_prompt_by_repo_name g = Github() diff --git a/server/routers/bot.py b/server/bot/router.py similarity index 98% rename from server/routers/bot.py rename to server/bot/router.py index e4dd2437..2b405926 100644 --- a/server/routers/bot.py +++ b/server/bot/router.py @@ -5,7 +5,7 @@ from typing import Annotated, Optional from bot.builder import bot_builder, bot_info_generator -from type_class.bot import BotUpdateRequest, BotCreateRequest +from core.type_class.bot import BotUpdateRequest, BotCreateRequest router = APIRouter( prefix="/api/bot", diff --git a/server/routers/chat.py b/server/chat/router.py similarity index 97% rename from server/routers/chat.py rename to server/chat/router.py index ed865b11..c1bb740f 100644 --- a/server/routers/chat.py +++ b/server/chat/router.py @@ -4,7 +4,7 @@ from petercat_utils.data_class import ChatData from agent import qa_chat, bot_builder -from verify.rate_limit import verify_rate_limit +from auth.rate_limit import verify_rate_limit from auth.get_user_info import get_user_access_token, get_user_id diff --git a/server/dao/BaseDAO.py b/server/core/dao/BaseDAO.py similarity index 100% rename from server/dao/BaseDAO.py rename to server/core/dao/BaseDAO.py diff --git a/server/dao/authorizationDAO.py b/server/core/dao/authorizationDAO.py similarity index 93% rename from server/dao/authorizationDAO.py rename to server/core/dao/authorizationDAO.py index 9fff5100..4964f169 100644 --- a/server/dao/authorizationDAO.py +++ b/server/core/dao/authorizationDAO.py @@ -2,8 +2,8 @@ from petercat_utils.db.client.supabase import get_client -from dao.BaseDAO import BaseDAO -from models.authorization import Authorization +from core.dao.BaseDAO import BaseDAO +from core.models.authorization import Authorization class AuthorizationDAO(BaseDAO): client: Client diff --git a/server/dao/botDAO.py b/server/core/dao/botDAO.py similarity index 85% rename from server/dao/botDAO.py rename to server/core/dao/botDAO.py index fc6e56c6..2561adbd 100644 --- a/server/dao/botDAO.py +++ b/server/core/dao/botDAO.py @@ -1,7 +1,7 @@ -from dao.BaseDAO import BaseDAO +from core.dao.BaseDAO import BaseDAO from supabase.client import Client -from models.bot import Bot +from core.models.bot import Bot from petercat_utils.db.client.supabase import get_client class BotDAO(BaseDAO): diff --git a/server/dao/repositoryConfigDAO.py b/server/core/dao/repositoryConfigDAO.py similarity index 93% rename from server/dao/repositoryConfigDAO.py rename to server/core/dao/repositoryConfigDAO.py index fff0e68a..9ef0b7c8 100644 --- a/server/dao/repositoryConfigDAO.py +++ b/server/core/dao/repositoryConfigDAO.py @@ -1,6 +1,6 @@ -from dao.BaseDAO import BaseDAO -from models.repository import RepositoryConfig +from core.dao.BaseDAO import BaseDAO +from core.models.repository import RepositoryConfig from supabase.client import Client from petercat_utils.db.client.supabase import get_client diff --git a/server/models/authorization.py b/server/core/models/authorization.py similarity index 100% rename from server/models/authorization.py rename to server/core/models/authorization.py diff --git a/server/core/models/bot.py b/server/core/models/bot.py new file mode 100644 index 00000000..750e07a8 --- /dev/null +++ b/server/core/models/bot.py @@ -0,0 +1,13 @@ +from datetime import datetime +from typing import Optional +from pydantic import BaseModel + +class Bot(BaseModel): + id: str + uid: str + avatar: Optional[str] = "" + description: Optional[str] + prompt: Optional[str] = "" + name: str + llm: Optional[str] = "openai" + created_at: datetime \ No newline at end of file diff --git a/server/models/repository.py b/server/core/models/repository.py similarity index 100% rename from server/models/repository.py rename to server/core/models/repository.py diff --git a/server/type_class/bot.py b/server/core/type_class/bot.py similarity index 100% rename from server/type_class/bot.py rename to server/core/type_class/bot.py diff --git a/server/docs/test.md b/server/docs/test.md deleted file mode 100644 index 03e1f8f1..00000000 --- a/server/docs/test.md +++ /dev/null @@ -1,197 +0,0 @@ ---- -order: 1 -title: 资源 -description: 这里汇总了与 Ant Design 相关的所有资源。 -toc: false ---- - -## 设计资源 - -这里提供 Ant Design 相关设计资源和设计工具的下载,更多设计资源正在整理和完善中。你可以在这个[地址](https://www.yuque.com/kitchen/topics/216)中反馈对新版本 Sketch Symbols 组件的意见。 - -
- - -- Sketch 组件包 - - https://gw.alipayobjects.com/zos/basement_prod/048ee28f-2c80-4d15-9aa3-4f5ddac50465.svg - - 桌面组件 Sketch 模板包 - - https://github.com/ant-design/ant-design/releases/download/5.13.3/AntDesign5.0_UI.KIT_202401.sketch - - 官方 -- Mobile Components - - https://gw.alipayobjects.com/zos/basement_prod/c0c3852c-d245-4330-886b-cb02ef49eb6d.svg - - 移动组件 Sketch 模板 - - https://gw.alipayobjects.com/os/bmw-prod/d6266aef-25b7-4892-b275-ce214121831c.sketch - - 官方 -- Ant Design Pro - - https://gw.alipayobjects.com/zos/basement_prod/5edc7f4d-3302-4710-963b-7b6c77ea8d06.svg - - 典型页面 + 通用业务模板 - - https://gw.alipayobjects.com/os/bmw-prod/22208f9d-f8c5-4d7c-b87a-fec290e96527.sketch - - 官方 -- Kitchen - - https://gw.alipayobjects.com/zos/basement_prod/d475d063-2754-4442-b9db-5d164e06acc9.svg - - Sketch 工具集 - - http://kitchen.alipay.com - - 官方 -- Ant Design Landing - - https://gw.alipayobjects.com/zos/basement_prod/b443f4be-5116-49b7-873f-a7c8502b8f0e.svg - - 首页模板集 - - https://landing.ant.design/docs/download-cn - - 官方 -- Ant Design 原型 (xiaopiu) - - https://gw.alipayobjects.com/zos/basement_prod/77e6a9ae-24a9-4be6-be42-f7fa8ee0eecf.svg - - 可在线编辑的组件库和交互原型 - - https://www.xiaopiu.com/topic/ant-design -- Figma 组件包 - - https://gw.alipayobjects.com/zos/basement_prod/7b9ed3f2-6f05-4ddb-bac3-d55feb71e0ac.svg - - 在 Figma 使用 Ant Design 进行设计 - - https://www.antforfigma.com -- Figma 开源组件包 - - https://gw.alipayobjects.com/zos/basement_prod/7b9ed3f2-6f05-4ddb-bac3-d55feb71e0ac.svg - - 代码级精确度的免费开源 Figma 完全组件库 - - https://www.figma.com/community/file/831698976089873405 -- 如意设计助手 - - https://github.com/ant-design/ant-design/assets/507615/45201521-37d0-4360-b81e-a1260dedad7a - - Figma 插件,使用 antd 代码组件库进行设计,交付对开发者友好的组件代码 - - https://www.figma.com/community/plugin/1192146318523533547 -- 全新 Chart 组件包 - - https://gw.alipayobjects.com/zos/basement_prod/a9dc586a-fe0a-4c7d-ab4f-f5ed779b963d.svg - - 桌面组件 Chart 模板包 - - https://gw.alipayobjects.com/os/bmw-prod/704968a5-2641-484e-9f65-c2735b2c0287.sketch - - 官方 -- 墨刀原型设计 - - https://cdn.modao.cc/logo_mockingbot.svg - - 内置丰富的 Ant Design 组件资源 - - https://modao.cc/square/ant-design -- 全套资源包(即时设计) - - https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*dxzdQYWlmjMAAAAAAAAAAAAAARQnAQ - - 可在「即时设计」在线免费使用的全套组件和模板 - - https://js.design/antd -- MasterGo 组件包 - - https://mastergo-local-default.oss-cn-beijing.aliyuncs.com/ant-design-mastergo.svg - - 可在「MasterGo」在线免费使用的全套组件和模板 - - https://mastergo.com/community/?utm_source=antdesign&utm_medium=link&utm_campaign=resource&cata_name=AntDesign -- Raycast 拓展 - - https://gw.alipayobjects.com/zos/basement_prod/5edc7f4d-3302-4710-963b-7b6c77ea8d06.svg - - mac 用户可使用 Raycast 快速打开 Ant Design 组件 - - https://www.raycast.com/crazyair/antd-open-browser - - -## 文章 - -想要了解 Ant Design 设计体系背后的故事?如何才能更好的应用 Ant Design?你可以查阅下述我们为你精挑细选的文章。也欢迎关注 [Ant Design 官方专栏](https://www.zhihu.com/column/c_1310524851418480640),这里常有关于 Ant Design 设计体系下相关话题内容的最新分享和讨论,如 Ant Design、AntV 可视化、Kitchen 设计插件、B 端产品设计、SaaS 产品设计、自然交互、增长设计、智能设计、设计工程化等。 - - - -## 致敬 - -在 Ant Design 4.0 的改版中,我们汲取顶级设计体系的精华,同时结合我们自身业务特性做了大量优化。我们希望通过不断努力和打磨,成为世界级设计体系的一份子,为「用户」和「设计者」带来极致体验。如果你也想追求卓越,建议去研究这些体系: [Fiori Design](https://experience.sap.com/fiori-design-web/)、 [Human Interface Guidelines](https://developer.apple.com/ios/human-interface-guidelines/overview/themes/)、 [Lightning Design System](https://lightningdesignsystem.com/getting-started/)、 [Material Design](https://material.io/) - -
- - -- About Face 4 #E1E8B7 - - https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*KKZWRozT8D8AAAAAAAAAAABkARQnAQ - - 一本数字产品和系统的交互设计指南 - - http://book.douban.com/subject/26642302/ -- Web 界面设计 #009C94 - - https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*yB0oQ42f0kEAAAAAAAAAAABkARQnAQ - - Web 界面的最佳实践、模式和原理 - - http://book.douban.com/subject/3821157/ -- 界面设计模式 #9489CF - - https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*aFAfTKIjR_IAAAAAAAAAAABkARQnAQ - - 界面设计总体思路指引 - - http://book.douban.com/subject/25716088/ -- 写给大家看的设计书 #AFBCC8 - - https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*tTvXQYApsIIAAAAAAAAAAABkARQnAQ - - 优秀设计所必须遵循的基本原则 - - http://book.douban.com/subject/3323633/ -- 设计心理学 1 #B7D9B7 - - https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*12W8R7nPxxUAAAAAAAAAAABkARQnAQ - - 强调以人为本的设计哲学 - - http://book.douban.com/subject/26102860/ -- 设计心理学 3 #EFBDB5 - - https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*a5VNSamt2EIAAAAAAAAAAABkARQnAQ - - 解释情感因素在设计领域扮演的角色 - - http://book.douban.com/subject/26424688/ -- Web 表单设计 #C2DAED - - https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*vXfQS7sStNYAAAAAAAAAAABkARQnAQ - - 表单设计的真谛 - - http://book.douban.com/subject/4886100/ - - -## 加入我们 - -蚂蚁集团 Ant Design 团队是一支兼具设计视角和工程视角的横向组织,服务蚂蚁集团上百个中后台系统,主打产品 Ant Design 服务全球 100 万设计师和工程师,是西湖区学院路西侧最具影响力的设计语言。欢迎来这里和我们一起打造优雅高效的人机设计/研发体系。 - -### UI/UE 设计师 - -简历和作品集请投递:jiayin.liu#antgroup.com - -> 注明简历来自 ant.design 官网 - -- 岗位级别:P5/P6/P7/P8 -- 岗位地点:杭州 -- 岗位要求: - - 至少 3-5 年的工作经验,扎实设计功底; - - 抽象能力强,善于透过表象找本质; - - 沟通能力佳,善于自我管理; - - 有企业级设计实战经验,加分; - - 有数据驱动的增长设计实践,加分; - - 深度理解 SAP、Salesforce、Google 等设计体系,能提出自己独到见解并落实到实践中,加加加分。 -- 岗位职责: - - 参与[蚂蚁链](https://blockchain.antgroup.com/)、人工智能、数据平台等企业级产品的设计工作; - - 参与[语雀](https://www.yuque.com/) 等创新产品的设计工作; - - 参与 Ant Design 的打磨,将其建设成全球卓越的设计体系。 - - 参与 AntV 的打磨,将其建设成全球一流的数据可视化体系。 -- One More Thing ❤️ : - - 你们总是为世界带去美好,但总是忘却你们也需要美好。我们正在努力打造 [🍳 Kitchen:一款为设计师提效的 Sketch 工具集](https://kitchen.alipay.com/)等专属设计师的产品,让设计真正变成财富。期待志同道合的你,一道给设计行业带来「微小而美好的改变」。 - -### 前端工程师 - -简历请投递:afc163+antd@gmail.com - -> 注明简历来自 ant.design 官网 - -- 岗位级别:P5/P6/P7/P8 -- 岗位地点:杭州/上海 -- 岗位要求: - - 在 React 技术栈持续耕耘,情有独钟。 - - 热爱开源。 - - 坚持和善于用技术和工具解决其他问题。 - - 丰富的中后台前端研发经验。 -- 岗位职责: - - 负责 Ant Design 前端基础设施研发。 - - 负责中后台设计/前端工具体系建设。 - -### Node.js 工程师 - -简历请投递:zhubin.gzb@antgroup.com - -> 注明简历来自 ant.design 官网 - -- 岗位级别:P5/P6/P7/P8 -- 岗位地点:杭州/上海 -- 岗位要求: - - 在 Node.js 技术栈持续耕耘,情有独钟。 - - 热爱开源。 - - 坚持和善于用技术和工具解决其他问题。 - - 丰富的 Node.js 研发经验。 -- 岗位职责: - - 负责 Node.js 前端基础设施研发。 - - 负责大前端工具体系建设。 - -### ADI(Artificial Design Intelligence) 工程师 - -简历和作品集请投递:jiayin.liu#antgroup.com - -> 注明简历来自 ant.design 官网 - -- 岗位级别:P7/P8 -- 岗位地点:杭州 -- 岗位要求: - - 有良好的工程师背景,善于学习和使用各类工具、框架解决研发问题; - - 对人工智能应用在设计行业,有坚定的信心和意愿; - - 已经有相关实践工作,优先考虑。 -- 岗位职责: - - 负责 Ant Design 工具体系和智能设计的研发,并配合团队成员进行商业化实践,把设计做成业务; - - 组建和培养有梯度的研发团队。 diff --git a/server/event_handler/issue.py b/server/event_handler/issue.py index f5b59a19..f595e4f3 100644 --- a/server/event_handler/issue.py +++ b/server/event_handler/issue.py @@ -2,7 +2,7 @@ from github import Github, Auth from github import GithubException -from dao.repositoryConfigDAO import RepositoryConfigDAO +from core.dao.repositoryConfigDAO import RepositoryConfigDAO from petercat_utils.data_class import ChatData, Message, TextContentBlock from agent.qa_chat import agent_chat diff --git a/server/utils/github.py b/server/github_app/handlers.py similarity index 53% rename from server/utils/github.py rename to server/github_app/handlers.py index 71a51c83..0b29aa73 100644 --- a/server/utils/github.py +++ b/server/github_app/handlers.py @@ -1,6 +1,5 @@ from typing import Union -import boto3 -from botocore.exceptions import ClientError + from petercat_utils import get_env_variable from github import Auth @@ -10,24 +9,6 @@ APP_ID = get_env_variable("X_GITHUB_APP_ID") -def get_private_key(): - secret_name = "prod/githubapp/petercat/pem" - region_name = "ap-northeast-1" - session = boto3.session.Session() - client = session.client( - service_name='secretsmanager', - region_name=region_name - ) - try: - get_secret_value_response = client.get_secret_value( - SecretId=secret_name - ) - except ClientError as e: - # For a list of exceptions thrown, see - # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html - raise e - - return get_secret_value_response['SecretString'] def get_handler(event: str, payload: dict, auth: Auth.AppAuth, installation_id: int) -> Union[PullRequestEventHandler, IssueEventHandler, DiscussionEventHandler, None]: handlers = { diff --git a/server/github_app/router.py b/server/github_app/router.py new file mode 100644 index 00000000..15e58b64 --- /dev/null +++ b/server/github_app/router.py @@ -0,0 +1,124 @@ +from typing import Annotated +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Header, Request, status +import logging +from fastapi.responses import RedirectResponse + +import time +from github import Auth, Github +from auth.get_user_info import get_user_access_token +from core.dao.authorizationDAO import AuthorizationDAO +from core.dao.repositoryConfigDAO import RepositoryConfigDAO +from core.models.repository import RepositoryConfig +from core.models.authorization import Authorization + +from github_app.handlers import get_handler +from github_app.utils import get_app_installations_access_token, get_installation_repositories, get_jwt, get_private_key + +from petercat_utils import get_env_variable + + +APP_ID = get_env_variable("X_GITHUB_APP_ID") +WEB_URL = get_env_variable("WEB_URL") + +logger = logging.getLogger() +logger.setLevel("INFO") + +router = APIRouter( + prefix="/api/github", + tags=["github"], + responses={404: {"description": "Not found"}}, +) + + +# 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": + return { + "success": False, + "message": f"Invalid setup_action value {setup_action}" + } + elif authorization_dao.exists(installation_id=installation_id): + return { + "success": False, + "message": f"Installation_id {installation_id} Exists" + } + 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) + print(f"github_app_callback: success={success}, message={message}") + + installed_repositories = get_installation_repositories( + access_token=access_token['token'] + ) + for repo in installed_repositories["repositories"]: + repository_config = RepositoryConfig( + repo_name=repo["full_name"], + 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 + ) + +@router.post("/app/webhook") +async def github_app_webhook( + request: Request, + background_tasks: BackgroundTasks, + x_github_event: str = Header(...), +): + payload = await request.json() + if "installation" not in payload: + return {"success": False, "message": "Invalid Webhook request"} + + installation_id = payload["installation"]["id"] + try: + auth = Auth.AppAuth( + app_id=APP_ID, private_key=get_private_key(), jwt_algorithm="RS256" + ).get_installation_auth(installation_id=int(installation_id)) + except Exception as e: + print("Failed", f"Authentication failed: {e}") + return {"success": False, "message": f"Authentication failed: {e}"} + + handler = get_handler( + x_github_event, payload, auth, installation_id=installation_id + ) + if handler: + await handler.execute() + return {"success": True} + else: + print("Failed, Unsupported GitHub event") + return {"success": False, "message": "Unsupported GitHub event"} + +@router.get("/user/organizations") +async def get_user_organizations( + user_access_token: Annotated[str | None, Depends(get_user_access_token)] = None +): + if user_access_token is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Github Login needed" + ) + auth = Auth.Token(token=user_access_token) + g = Github(auth=auth) + user = g.get_user() + orgs = user.get_orgs() + + return [org.raw_data for org in orgs] diff --git a/server/github_app/utils.py b/server/github_app/utils.py new file mode 100644 index 00000000..06e7b0af --- /dev/null +++ b/server/github_app/utils.py @@ -0,0 +1,70 @@ +import boto3 +import jwt +import requests +from botocore.exceptions import ClientError +import time + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.backends import default_backend + +from petercat_utils.utils.env import get_env_variable + +APP_ID = get_env_variable("X_GITHUB_APP_ID") + +def get_private_key(): + secret_name = "prod/githubapp/petercat/pem" + region_name = "ap-northeast-1" + session = boto3.session.Session() + client = session.client( + service_name='secretsmanager', + region_name=region_name + ) + try: + get_secret_value_response = client.get_secret_value( + SecretId=secret_name + ) + except ClientError as e: + # For a list of exceptions thrown, see + # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html + raise e + + return get_secret_value_response['SecretString'] + +def get_jwt(): + payload = { + # Issued at time + 'iat': int(time.time()), + # JWT expiration time (10 minutes maximum) + 'exp': int(time.time()) + 600, + # GitHub App's identifier + 'iss': APP_ID + } + + pem = get_private_key() + private_key = serialization.load_pem_private_key( + pem.encode("utf-8"), password=None, backend=default_backend() + ) + return jwt.encode(payload, private_key, algorithm='RS256') + +def get_app_installations_access_token(installation_id: str, jwt: str): + url = f"https://api.github.com/app/installations/{installation_id}/access_tokens" + print("get_app_installations_access_token", url, jwt) + resp = requests.post(url, + headers={ + 'X-GitHub-Api-Version': '2022-11-28', + 'Accept': 'application/vnd.github+json', + 'Authorization': f"Bearer {jwt}" + } + ) + + return resp.json() + +def get_installation_repositories(access_token: str): + url = "https://api.github.com/installation/repositories" + print("get_installation_repositories", url) + resp = requests.get(url, headers={ + 'X-GitHub-Api-Version': '2022-11-28', + 'Accept': 'application/vnd.github+json', + 'Authorization': f"Bearer {access_token}" + }) + return resp.json() \ No newline at end of file diff --git a/server/main.py b/server/main.py index 296457c1..e0c56b58 100644 --- a/server/main.py +++ b/server/main.py @@ -10,7 +10,12 @@ # Import fastapi routers -from routers import bot, health_checker, github, rag, auth, chat, task +from auth import router as auth_router +from bot import router as bot_router +from chat import router as chat_router +from rag import router as rag_router +from task import router as task_router +from github_app import router as github_app_router AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") API_AUDIENCE = get_env_variable("API_IDENTIFIER") @@ -43,13 +48,17 @@ ) -app.include_router(health_checker.router) -app.include_router(github.router) -app.include_router(rag.router) -app.include_router(bot.router) -app.include_router(auth.router) -app.include_router(chat.router) -app.include_router(task.router) +app.include_router(rag_router.router) +app.include_router(bot_router.router) +app.include_router(auth_router.router) +app.include_router(chat_router.router) +app.include_router(task_router.router) +app.include_router(github_app_router.router) + + +@app.get("/api/health_checker") +def health_checker(): + return { "Hello": "World" } if __name__ == "__main__": diff --git a/server/models/bot.py b/server/models/bot.py deleted file mode 100644 index 79fa139f..00000000 --- a/server/models/bot.py +++ /dev/null @@ -1,12 +0,0 @@ -from datetime import datetime -from pydantic import BaseModel - -class Bot(BaseModel): - id: str - uid: str - avatar: str - description: str - prompt: str - name: str - llm: str - created_at: datetime \ No newline at end of file diff --git a/server/routers/rag.py b/server/rag/router.py similarity index 98% rename from server/routers/rag.py rename to server/rag/router.py index a919f11c..91e4f1dd 100644 --- a/server/routers/rag.py +++ b/server/rag/router.py @@ -13,7 +13,7 @@ git_issue_task, ) -from verify.rate_limit import verify_rate_limit +from auth.rate_limit import verify_rate_limit router = APIRouter( diff --git a/server/routers/__init__.py b/server/routers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/server/routers/github.py b/server/routers/github.py deleted file mode 100644 index 0dc4e8e8..00000000 --- a/server/routers/github.py +++ /dev/null @@ -1,151 +0,0 @@ -from typing import Annotated -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Header, Request, status -import logging -from fastapi.responses import RedirectResponse -import requests -import time -from github import Auth, Github -from auth.get_user_info import get_user_access_token -from dao.authorizationDAO import AuthorizationDAO -from dao.repositoryConfigDAO import RepositoryConfigDAO -from models.repository import RepositoryConfig -from models.authorization import Authorization -from utils.github import get_handler, get_private_key -from petercat_utils import get_env_variable - -import jwt -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.backends import default_backend - -APP_ID = get_env_variable("X_GITHUB_APP_ID") -WEB_URL = get_env_variable("WEB_URL") - -logger = logging.getLogger() -logger.setLevel("INFO") - -router = APIRouter( - prefix="/api/github", - tags=["github"], - responses={404: {"description": "Not found"}}, -) - -def get_jwt(): - payload = { - # Issued at time - 'iat': int(time.time()), - # JWT expiration time (10 minutes maximum) - 'exp': int(time.time()) + 600, - # GitHub App's identifier - 'iss': APP_ID - } - - pem = get_private_key() - private_key = serialization.load_pem_private_key( - pem.encode("utf-8"), password=None, backend=default_backend() - ) - return jwt.encode(payload, private_key, algorithm='RS256') - -def get_app_installations_access_token(installation_id: str, jwt: str): - url = f"https://api.github.com/app/installations/{installation_id}/access_tokens" - print("get_app_installations_access_token", url, jwt) - resp = requests.post(url, - headers={ - 'X-GitHub-Api-Version': '2022-11-28', - 'Accept': 'application/vnd.github+json', - 'Authorization': f"Bearer {jwt}" - } - ) - - return resp.json() - -def get_installation_repositories(access_token: str): - url = "https://api.github.com/installation/repositories" - print("get_installation_repositories", url) - resp = requests.get(url, headers={ - 'X-GitHub-Api-Version': '2022-11-28', - 'Accept': 'application/vnd.github+json', - 'Authorization': f"Bearer {access_token}" - }) - return resp.json() - - -# 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": - return { "success": False, "message": f"Invalid setup_action value {setup_action}" } - elif authorization_dao.exists(installation_id=installation_id): - return { "success": False, "message": f"Installation_id {installation_id} Exists" } - 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) - print(f"github_app_callback: success={success}, message={message}") - - installed_repositories = get_installation_repositories(access_token=access_token['token']) - for repo in installed_repositories["repositories"]: - repository_config = RepositoryConfig(repo_name=repo["full_name"], 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) - -@router.post("/app/webhook") -async def github_app_webhook( - request: Request, - background_tasks: BackgroundTasks, - x_github_event: str = Header(...), -): - payload = await request.json() - if "installation" not in payload: - return {"success": False, "message": "Invalid Webhook request"} - - installation_id = payload["installation"]["id"] - try: - auth = Auth.AppAuth( - app_id=APP_ID, private_key=get_private_key(), jwt_algorithm="RS256" - ).get_installation_auth(installation_id=int(installation_id)) - except Exception as e: - print("Failed", f"Authentication failed: {e}") - return {"success": False, "message": f"Authentication failed: {e}"} - - handler = get_handler( - x_github_event, payload, auth, installation_id=installation_id - ) - if handler: - await handler.execute() - return {"success": True} - else: - print("Failed, Unsupported GitHub event") - return {"success": False, "message": "Unsupported GitHub event"} - -@router.get("/user/organizations") -async def get_user_organizations(user_access_token: Annotated[str | None, Depends(get_user_access_token)] = None): - if user_access_token is None: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Github Login needed") - auth = Auth.Token(token=user_access_token) - g = Github(auth=auth) - user = g.get_user() - orgs = user.get_orgs() - - return [org.raw_data for org in orgs] - -@router.get("/orgs/{org_id}/repos") -async def get_org_repos(org_id: str, user_access_token: Annotated[str | None, Depends(get_user_access_token)] = None): - if user_access_token is None: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Github Login needed") - auth = Auth.Token(token=user_access_token) - g = Github(auth=auth) - org = g.get_organization(org_id) - repos = org.get_repos() - print(f"repos={repos}") - return [repo.raw_data for repo in repos] diff --git a/server/routers/health_checker.py b/server/routers/health_checker.py deleted file mode 100644 index cfd90517..00000000 --- a/server/routers/health_checker.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter( - prefix="/api", - tags=["health_checkers"], - responses={404: {"description": "Not found"}}, -) - -@router.get("/health_checker") -def health_checker(): - return { "Hello": "World" } diff --git a/server/sql/rag_docs.sql b/server/sql/rag_docs.sql deleted file mode 100644 index 8bb667ef..00000000 --- a/server/sql/rag_docs.sql +++ /dev/null @@ -1,104 +0,0 @@ --- Supabase AI is experimental and may produce incorrect answers --- Always verify the output before executing - --- Enable the pgvector extension to work with embedding vectors -create extension -if not exists vector; - --- Create a table to store your rag_docs -create table rag_docs -( - id uuid primary key, - content text, - -- corresponds to Document.pageContent - metadata jsonb, - -- corresponds to Document.metadata - embedding vector (1536), - -- 1536 works for OpenAI embeddings, change if needed - -- per request info - repo_name varchar, - commit_id varchar, - bot_id varchar, - file_sha varchar, - file_path varchar -); - -create table rag_issues -( - id uuid primary key, - content text, - -- corresponds to Document.pageContent - metadata jsonb, - -- corresponds to Document.metadata - embedding vector (1536), - -- 1536 works for OpenAI embeddings, change if needed - -- per request info - repo_name varchar, - issue_id varchar, - bot_id varchar -); - --- Drop the existing function if it already exists -drop function if exists match_rag_docs; - -create function match_rag_docs - ( - query_embedding vector (1536), - filter jsonb default '{}' -) returns table -( - id uuid, - content text, - metadata jsonb, - embedding vector, - similarity float -) language plpgsql as $$ -#variable_conflict use_column -begin - return query - select - id, - content, - metadata, - embedding, - 1 - (rag_docs.embedding <=> query_embedding - ) as similarity - from rag_docs - where metadata @> jsonb_extract_path(filter, 'metadata') - and bot_id = jsonb_extract_path_text(filter, 'bot_id') - order by rag_docs.embedding <=> query_embedding; -end; -$$; - - --- Drop the existing function if it already exists -drop function if exists match_rag_issues; - -create function match_rag_issues - ( - query_embedding vector (1536), - filter jsonb default '{}' -) returns table -( - id uuid, - content text, - metadata jsonb, - embedding vector, - similarity float -) language plpgsql as $$ -#variable_conflict use_column -begin - return query - select - id, - content, - metadata, - embedding, - 1 - (rag_issues.embedding <=> query_embedding - ) as similarity - from rag_issues - where metadata @> jsonb_extract_path(filter, 'metadata') - and bot_id = jsonb_extract_path_text(filter, 'bot_id') - order by rag_issues.embedding <=> query_embedding; -end; -$$; diff --git a/server/routers/task.py b/server/task/router.py similarity index 100% rename from server/routers/task.py rename to server/task/router.py