diff --git a/.aws/petercat-preview.toml b/.aws/petercat-preview.toml index c1b39016..a748542d 100644 --- a/.aws/petercat-preview.toml +++ b/.aws/petercat-preview.toml @@ -7,4 +7,4 @@ region = "ap-northeast-1" confirm_changeset = true capabilities = "CAPABILITY_IAM" disable_rollback = true -image_repositories = ["FastAPIFunction=654654285942.dkr.ecr.ap-northeast-1.amazonaws.com/petercatapipreview49199518/fastapifunctionead79d0drepo", "SQSSubscriptionFunction=654654285942.dkr.ecr.ap-northeast-1.amazonaws.com/petercatapipreview49199518/sqssubscriptionfunctiona2fc8b7drepo"] +image_repositories = ["FastAPIFunction=654654285942.dkr.ecr.ap-northeast-1.amazonaws.com/petercatapipreview49199518/fastapifunctionead79d0drepo"] diff --git a/.aws/petercat-prod.toml b/.aws/petercat-prod.toml index 08f8429f..2c1d5d45 100644 --- a/.aws/petercat-prod.toml +++ b/.aws/petercat-prod.toml @@ -7,4 +7,4 @@ region = "ap-northeast-1" confirm_changeset = true capabilities = "CAPABILITY_IAM" disable_rollback = true -image_repositories = ["FastAPIFunction=654654285942.dkr.ecr.ap-northeast-1.amazonaws.com/samapp7427b055/fastapifunctionead79d0drepo", "SQSSubscriptionFunction=654654285942.dkr.ecr.ap-northeast-1.amazonaws.com/samapp7427b055/sqssubscriptionfunctiona2fc8b7drepo"] +image_repositories = ["FastAPIFunction=654654285942.dkr.ecr.ap-northeast-1.amazonaws.com/samapp7427b055/fastapifunctionead79d0drepo"] diff --git a/server/.env.example b/server/.env.example index ce5a272d..b373b571 100644 --- a/server/.env.example +++ b/server/.env.example @@ -3,106 +3,9 @@ # YOU MUST CHANGE THESE BEFORE GOING INTO PRODUCTION ############ -POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password -JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long -ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE -SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q -DASHBOARD_USERNAME=supabase -DASHBOARD_PASSWORD=this_password_is_insecure_and_should_be_updated - -############ -# Database - You can change these to any PostgreSQL database that has logical replication enabled. -############ - -POSTGRES_HOST=db -POSTGRES_DB=postgres -POSTGRES_PORT=5432 -# default user is postgres - -############ -# API Proxy - Configuration for the Kong Reverse proxy. -############ - -KONG_HTTP_PORT=8000 -KONG_HTTPS_PORT=8443 - - -############ -# API - Configuration for PostgREST. -############ - -PGRST_DB_SCHEMAS=public,storage,graphql_public - - -############ -# Auth - Configuration for the GoTrue authentication server. -############ - -## General -SITE_URL=http://localhost:3000 -ADDITIONAL_REDIRECT_URLS= -JWT_EXPIRY=3600 -DISABLE_SIGNUP=false -API_EXTERNAL_URL=http://localhost:8000 - -## Mailer Config -MAILER_URLPATHS_CONFIRMATION="/auth/v1/verify" -MAILER_URLPATHS_INVITE="/auth/v1/verify" -MAILER_URLPATHS_RECOVERY="/auth/v1/verify" -MAILER_URLPATHS_EMAIL_CHANGE="/auth/v1/verify" - -## Email auth -ENABLE_EMAIL_SIGNUP=true -ENABLE_EMAIL_AUTOCONFIRM=false -SMTP_ADMIN_EMAIL=admin@example.com -SMTP_HOST=supabase-mail -SMTP_PORT=2500 -SMTP_USER=fake_mail_user -SMTP_PASS=fake_mail_password -SMTP_SENDER_NAME=fake_sender - -## Phone auth -ENABLE_PHONE_SIGNUP=true -ENABLE_PHONE_AUTOCONFIRM=true - - -############ -# Studio - Configuration for the Dashboard -############ - -STUDIO_DEFAULT_ORGANIZATION=Default Organization -STUDIO_DEFAULT_PROJECT=Default Project - -STUDIO_PORT=3000 -# replace if you intend to use Studio outside of localhost -SUPABASE_PUBLIC_URL=http://localhost:8000 - -# Enable webp support -IMGPROXY_ENABLE_WEBP_DETECTION=true - -############ -# Functions - Configuration for Functions -############ -# NOTE: VERIFY_JWT applies to all functions. Per-function VERIFY_JWT is not supported yet. -FUNCTIONS_VERIFY_JWT=false - -############ -# Logs - Configuration for Logflare -# Please refer to https://supabase.com/docs/reference/self-hosting-analytics/introduction -############ - -LOGFLARE_LOGGER_BACKEND_API_KEY=your-super-secret-and-long-logflare-key - -# Change vector.toml sinks to reflect this change -LOGFLARE_API_KEY=your-super-secret-and-long-logflare-key - -# Docker socket location - this value will differ depending on your OS -DOCKER_SOCKET_LOCATION=/var/run/docker.sock - -# Google Cloud Project details -GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID -GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER - #TAVILY_API_KEY TAVILY_API_KEY=TAVILY_API_KEY + +GITHUB_APP_CLIENT_ID=Iv1.c2e88b429e541264 +GITHUB_APP_CLIENT_SECRET=xxx \ No newline at end of file diff --git a/server/db/supabase/client.py b/server/db/supabase/client.py new file mode 100644 index 00000000..ceb53cab --- /dev/null +++ b/server/db/supabase/client.py @@ -0,0 +1,9 @@ +from supabase.client import Client, create_client +from uilts.env import get_env_variable + +supabase_url = get_env_variable("SUPABASE_URL") +supabase_key = get_env_variable("SUPABASE_SERVICE_KEY") + +def get_client(): + supabase: Client = create_client(supabase_url, supabase_key) + return supabase diff --git a/server/event_handler/pull_request.py b/server/event_handler/pull_request.py new file mode 100644 index 00000000..b15b5b79 --- /dev/null +++ b/server/event_handler/pull_request.py @@ -0,0 +1,27 @@ + +from typing import Any, Dict, Union +from typing_extensions import NotRequired, TypedDict +from github import GithubObject, PullRequest, Repository, Organization, Installation, PullRequestComment +from github import Github, Auth + +class PullRequestEventHandler(): + event: Any + auth: Auth.AppAuth + g: Github + + def __init__(self, payload, auth: Auth.AppAuth) -> None: + self.event = payload + self.auth = auth + self.g = Github(auth=auth) + + def execute(self): + match self.event['action']: + case 'opened': + repo = self.g.get_repo(self.event['repository']["full_name"]) + pr = repo.get_pull(self.event["pull_request"]["number"]) + comment = pr.create_issue_comment("This is a comment from PeterCat") + + print(repo, pr, comment) + return { "success": True } + case _: + return { "success": True } \ No newline at end of file diff --git a/server/main.py b/server/main.py index 1427de61..1b4e6785 100644 --- a/server/main.py +++ b/server/main.py @@ -6,18 +6,21 @@ from fastapi.middleware.cors import CORSMiddleware from agent import stream + from uilts.env import get_env_variable -from data_class import ChatData, ExecuteMessage -from message_queue.queue_wrapper import delete_messages, get_queue, receive_messages, send_message, unpack_message +from data_class import ChatData + +# Import fastapi routers +from routers import health_checker, github open_api_key = get_env_variable("OPENAI_API_KEY") -sqs_queue_name = get_env_variable("PETERCAT_EX_SQS") +is_dev = bool(get_env_variable("IS_DEV")) app = FastAPI( title="Bo-meta Server", version="1.0", description="Agent Chat APIs" - ) +) app.add_middleware( CORSMiddleware, @@ -28,19 +31,8 @@ expose_headers=["*"], ) -@app.get("/") -def read_root(): - return {"Hello": "World"} - -@app.post("/api/message") -def send_sqs_message(message: ExecuteMessage): - queue = get_queue(sqs_queue_name) - return send_message(queue=queue, message=message) - -@app.get("/api/message/receive") -def receive_sqs_message(): - queue = get_queue(sqs_queue_name) - return StreamingResponse(receive_messages(queue), media_type="text/event-stream") +app.include_router(health_checker.router) +app.include_router(github.router) @app.post("/api/chat/stream", response_class=StreamingResponse) @@ -59,4 +51,7 @@ def search_knowledge(query: str): return data if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080"))) + if is_dev: + uvicorn.run("main:app", host="0.0.0.0", port=int(os.environ.get("PORT", "8080")), reload=True) + else: + uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080"))) \ No newline at end of file diff --git a/server/message_queue/queue_wrapper.py b/server/message_queue/queue_wrapper.py deleted file mode 100644 index 58c37977..00000000 --- a/server/message_queue/queue_wrapper.py +++ /dev/null @@ -1,102 +0,0 @@ -import json -import boto3 -from botocore.exceptions import ClientError -import logging - -from data_class import ExecuteMessage - -logger = logging.getLogger(__name__) -sqs = boto3.resource("sqs") - -def get_queue(name): - try: - queue = sqs.get_queue_by_name(QueueName=name) - except ClientError as error: - logger.exception("Couldn't get queue named %s.", name) - raise error - else: - return queue - -def send_message(queue, message: ExecuteMessage, message_attributes=None): - if not message_attributes: - message_attributes = { - "type": { "StringValue": message.type, "DataType": "String" }, - "repo": { "StringValue": message.repo, "DataType": "String" }, - "path": { "StringValue": message.path, "DataType": "String" }, - } - - message_body = encode_message(message=message) - - try: - response = queue.send_message( - MessageBody=message_body, MessageAttributes=message_attributes - ) - - except ClientError as error: - logger.exception("Send message failed: %s", message_body) - raise error - else: - return response - -async def receive_messages(queue, max_number = 10, wait_time = 2): - try: - messages = queue.receive_messages( - MessageAttributeNames=["All"], - MaxNumberOfMessages=max_number, - WaitTimeSeconds=wait_time, - ) - for msg in messages: - logger.info("Received message: %s: %s", msg.message_id, msg.body) - type, repo, path = unpack_message(msg) - yield json.dumps({ "type": type, "repo": repo, "path": path }) - delete_messages(queue, messages) - - except ClientError as error: - logger.exception("Couldn't receive messages from queue: %s", queue) - raise error - - -def delete_messages(queue, messages): - """ - Delete a batch of messages from a queue in a single request. - - :param queue: The queue from which to delete the messages. - :param messages: The list of messages to delete. - :return: The response from SQS that contains the list of successful and failed - message deletions. - """ - try: - entries = [ - {"Id": str(ind), "ReceiptHandle": msg.receipt_handle} - for ind, msg in enumerate(messages) - ] - response = queue.delete_messages(Entries=entries) - if "Successful" in response: - for msg_meta in response["Successful"]: - logger.info("Deleted %s", messages[int(msg_meta["Id"])].receipt_handle) - if "Failed" in response: - for msg_meta in response["Failed"]: - logger.warning( - "Could not delete %s", messages[int(msg_meta["Id"])].receipt_handle - ) - except ClientError: - logger.exception("Couldn't delete messages from queue %s", queue) - else: - return response - -def encode_message(message: ExecuteMessage): - return json.dumps({ - "type": message.type, - "repo": message.repo, - "path": message.path, - }) - -def unpack_message(msg): - if (msg is None): - return (f"", f"", f"") - else: - return ( - msg.message_attributes["type"]["StringValue"], - msg.message_attributes["repo"]["StringValue"], - msg.message_attributes["path"]["StringValue"], - ) \ No newline at end of file diff --git a/server/rag/retrieval.py b/server/rag/retrieval.py index 86e2c190..2c48f163 100644 --- a/server/rag/retrieval.py +++ b/server/rag/retrieval.py @@ -4,7 +4,7 @@ from langchain_openai import OpenAIEmbeddings from langchain_text_splitters import CharacterTextSplitter from langchain_community.vectorstores import SupabaseVectorStore -from supabase.client import Client, create_client +from db.supabase.client import get_client from uilts.env import get_env_variable supabase_url = get_env_variable("SUPABASE_URL") @@ -13,8 +13,6 @@ query_name="match_antd_knowledge" chunk_size=500 -supabase: Client = create_client(supabase_url, supabase_key) - def convert_document_to_dict(document): return { 'page_content': document.page_content, @@ -26,7 +24,7 @@ def init_retriever(): embeddings = OpenAIEmbeddings() db = SupabaseVectorStore( embedding=embeddings, - client=supabase, + client=get_client(), table_name=table_name, query_name=query_name, chunk_size=chunk_size, diff --git a/server/requirements.txt b/server/requirements.txt index 2a781d52..854dfc4c 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -13,4 +13,6 @@ python-multipart httpx[socks] load_dotenv supabase -boto3>=1.26.79 +boto3>=1.34.84 +pyjwt>=2.4.0 +pydantic>=2.7.0 \ No newline at end of file diff --git a/server/routers/github.py b/server/routers/github.py new file mode 100644 index 00000000..474ddd26 --- /dev/null +++ b/server/routers/github.py @@ -0,0 +1,65 @@ +from fastapi import APIRouter, BackgroundTasks, Header, Request +import logging +import boto3 +from botocore.exceptions import ClientError +# from jwt import JWT, jwk_from_pem +from event_handler.pull_request import PullRequestEventHandler +from github import Auth + +from uilts.env import get_env_variable + +APP_ID = get_env_variable("GITHUB_APP_ID") +CLIENT_ID = get_env_variable("GITHUB_APP_CLIENT_ID") +CLIENT_SECRET = get_env_variable("GITHUB_APP_CLIENT_SECRET") + + +logger = logging.getLogger() +logger.setLevel("INFO") + +router = APIRouter( + prefix="/api/github", + tags=["health_checkers"], + responses={404: {"description": "Not found"}}, +) + +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'] + +# 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): + return { "success": True } + +@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" in payload: + installation_id = payload["installation"]["id"] + auth = Auth.AppAuth(app_id=APP_ID, private_key=get_private_key(), jwt_algorithm="RS256").get_installation_auth(installation_id=int(installation_id)) + + match x_github_event: + case 'pull_request': + handler = PullRequestEventHandler(payload=payload, auth=auth) + handler.execute() + case _: + return { "success": True } + else: + return { "success": False, "message": "Invalid Webhook request"} + diff --git a/server/routers/health_checker.py b/server/routers/health_checker.py new file mode 100644 index 00000000..e4c078f3 --- /dev/null +++ b/server/routers/health_checker.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter, Depends, HTTPException + +router = APIRouter( + prefix="/api", + tags=["health_checkers"], + responses={404: {"description": "Not found"}}, +) + +@router.get("/health_checker") +def health_checker(): + return {"Hello": "World"} \ No newline at end of file diff --git a/subscriber/Dockerfile b/subscriber/Dockerfile deleted file mode 100644 index 0df1de9f..00000000 --- a/subscriber/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM public.ecr.aws/lambda/python:3.12 - -# Copy requirements.txt -COPY requirements.txt ${LAMBDA_TASK_ROOT} - -# Install the specified packages -RUN pip install -r requirements.txt - -# Copy function code -COPY sqs_subscriber.py ${LAMBDA_TASK_ROOT} - -# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) -CMD [ "sqs_subscriber.lambda_handler" ] \ No newline at end of file diff --git a/subscriber/requirements.txt b/subscriber/requirements.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/subscriber/sqs_subscriber.py b/subscriber/sqs_subscriber.py deleted file mode 100644 index b1403e47..00000000 --- a/subscriber/sqs_subscriber.py +++ /dev/null @@ -1,16 +0,0 @@ -import json - -def lambda_handler(event, context): - if event: - batch_item_failures = [] - sqs_batch_response = {} - - for record in event["Records"]: - try: - # process message - print(f"receive message here") - except Exception as e: - batch_item_failures.append({"itemIdentifier": record['messageId']}) - - sqs_batch_response["batchItemFailures"] = batch_item_failures - return sqs_batch_response \ No newline at end of file diff --git a/template.yml b/template.yml index 521ce47b..717d7b57 100644 --- a/template.yml +++ b/template.yml @@ -33,37 +33,10 @@ Resources: DockerContext: server DockerTag: v1 - SQSSubscriptionFunction: - Type: AWS::Serverless::Function - Properties: - PackageType: Image - MemorySize: 512 - FunctionUrlConfig: - AuthType: NONE - Policies: - - Statement: - - Sid: BedrockInvokePolicy - Effect: Allow - Action: - - bedrock:InvokeModelWithResponseStream - Resource: '*' - Tracing: Active - Metadata: - Dockerfile: Dockerfile - DockerContext: subscriber - DockerTag: v1 - Outputs: FastAPIFunctionUrl: Description: "Function URL for FastAPI function" Value: !GetAtt FastAPIFunctionUrl.FunctionUrl FastAPIFunction: Description: "FastAPI Lambda Function ARN" - Value: !GetAtt FastAPIFunction.Arn - - SQSSubscriptionFunctionUrl: - Description: "Function URL for SQS Subscriptio function" - Value: !GetAtt FastAPIFunctionUrl.FunctionUrl - SQSSubscriptionFunction: - Description: "SQS Subscription Function Lambda Function ARN" - Value: !GetAtt SQSSubscriptionFunction.Arn \ No newline at end of file + Value: !GetAtt FastAPIFunction.Arn \ No newline at end of file diff --git a/tests/github/pull_request_event.json b/tests/github/pull_request_event.json new file mode 100644 index 00000000..faea46e9 --- /dev/null +++ b/tests/github/pull_request_event.json @@ -0,0 +1,507 @@ +{ + "action": "opened", + "number": 4, + "pull_request": { + "url": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls/4", + "id": 1821903248, + "node_id": "PR_kwDOKxegYM5smAmQ", + "html_url": "https://github.com/ant-xuexiao/demo-repository/pull/4", + "diff_url": "https://github.com/ant-xuexiao/demo-repository/pull/4.diff", + "patch_url": "https://github.com/ant-xuexiao/demo-repository/pull/4.patch", + "issue_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/4", + "number": 4, + "state": "open", + "locked": false, + "title": "Update README.md", + "user": { + "login": "RaoHai", + "id": 566097, + "node_id": "MDQ6VXNlcjU2NjA5Nw==", + "avatar_url": "https://avatars.githubusercontent.com/u/566097?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/RaoHai", + "html_url": "https://github.com/RaoHai", + "followers_url": "https://api.github.com/users/RaoHai/followers", + "following_url": "https://api.github.com/users/RaoHai/following{/other_user}", + "gists_url": "https://api.github.com/users/RaoHai/gists{/gist_id}", + "starred_url": "https://api.github.com/users/RaoHai/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/RaoHai/subscriptions", + "organizations_url": "https://api.github.com/users/RaoHai/orgs", + "repos_url": "https://api.github.com/users/RaoHai/repos", + "events_url": "https://api.github.com/users/RaoHai/events{/privacy}", + "received_events_url": "https://api.github.com/users/RaoHai/received_events", + "type": "User", + "site_admin": false + }, + "body": null, + "created_at": "2024-04-14T11:05:29Z", + "updated_at": "2024-04-14T11:05:29Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls/4/commits", + "review_comments_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls/4/comments", + "review_comment_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/4/comments", + "statuses_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/statuses/bf881bd623bd5a27839576e68c192d4b6daaeabb", + "head": { + "label": "ant-xuexiao:RaoHai-patch-1", + "ref": "RaoHai-patch-1", + "sha": "bf881bd623bd5a27839576e68c192d4b6daaeabb", + "user": { + "login": "ant-xuexiao", + "id": 151921220, + "node_id": "O_kgDOCQ4iRA", + "avatar_url": "https://avatars.githubusercontent.com/u/151921220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ant-xuexiao", + "html_url": "https://github.com/ant-xuexiao", + "followers_url": "https://api.github.com/users/ant-xuexiao/followers", + "following_url": "https://api.github.com/users/ant-xuexiao/following{/other_user}", + "gists_url": "https://api.github.com/users/ant-xuexiao/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ant-xuexiao/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ant-xuexiao/subscriptions", + "organizations_url": "https://api.github.com/users/ant-xuexiao/orgs", + "repos_url": "https://api.github.com/users/ant-xuexiao/repos", + "events_url": "https://api.github.com/users/ant-xuexiao/events{/privacy}", + "received_events_url": "https://api.github.com/users/ant-xuexiao/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 722968672, + "node_id": "R_kgDOKxegYA", + "name": "demo-repository", + "full_name": "ant-xuexiao/demo-repository", + "private": true, + "owner": { + "login": "ant-xuexiao", + "id": 151921220, + "node_id": "O_kgDOCQ4iRA", + "avatar_url": "https://avatars.githubusercontent.com/u/151921220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ant-xuexiao", + "html_url": "https://github.com/ant-xuexiao", + "followers_url": "https://api.github.com/users/ant-xuexiao/followers", + "following_url": "https://api.github.com/users/ant-xuexiao/following{/other_user}", + "gists_url": "https://api.github.com/users/ant-xuexiao/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ant-xuexiao/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ant-xuexiao/subscriptions", + "organizations_url": "https://api.github.com/users/ant-xuexiao/orgs", + "repos_url": "https://api.github.com/users/ant-xuexiao/repos", + "events_url": "https://api.github.com/users/ant-xuexiao/events{/privacy}", + "received_events_url": "https://api.github.com/users/ant-xuexiao/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/ant-xuexiao/demo-repository", + "description": "A code repository designed to show the best GitHub has to offer.", + "fork": false, + "url": "https://api.github.com/repos/ant-xuexiao/demo-repository", + "forks_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/forks", + "keys_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/teams", + "hooks_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/hooks", + "issue_events_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/events{/number}", + "events_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/events", + "assignees_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/assignees{/user}", + "branches_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/branches{/branch}", + "tags_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/tags", + "blobs_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/statuses/{sha}", + "languages_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/languages", + "stargazers_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/stargazers", + "contributors_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/contributors", + "subscribers_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/subscribers", + "subscription_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/subscription", + "commits_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/contents/{+path}", + "compare_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/merges", + "archive_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/downloads", + "issues_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues{/number}", + "pulls_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls{/number}", + "milestones_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/milestones{/number}", + "notifications_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/labels{/name}", + "releases_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/releases{/id}", + "deployments_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/deployments", + "created_at": "2023-11-24T11:20:30Z", + "updated_at": "2023-11-24T11:20:35Z", + "pushed_at": "2024-04-14T11:05:29Z", + "git_url": "git://github.com/ant-xuexiao/demo-repository.git", + "ssh_url": "git@github.com:ant-xuexiao/demo-repository.git", + "clone_url": "https://github.com/ant-xuexiao/demo-repository.git", + "svn_url": "https://github.com/ant-xuexiao/demo-repository", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": "HTML", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 1, + "license": null, + "allow_forking": false, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "private", + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "base": { + "label": "ant-xuexiao:main", + "ref": "main", + "sha": "1e039bd098659b5c6ecec09c7abd3403e01a5789", + "user": { + "login": "ant-xuexiao", + "id": 151921220, + "node_id": "O_kgDOCQ4iRA", + "avatar_url": "https://avatars.githubusercontent.com/u/151921220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ant-xuexiao", + "html_url": "https://github.com/ant-xuexiao", + "followers_url": "https://api.github.com/users/ant-xuexiao/followers", + "following_url": "https://api.github.com/users/ant-xuexiao/following{/other_user}", + "gists_url": "https://api.github.com/users/ant-xuexiao/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ant-xuexiao/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ant-xuexiao/subscriptions", + "organizations_url": "https://api.github.com/users/ant-xuexiao/orgs", + "repos_url": "https://api.github.com/users/ant-xuexiao/repos", + "events_url": "https://api.github.com/users/ant-xuexiao/events{/privacy}", + "received_events_url": "https://api.github.com/users/ant-xuexiao/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 722968672, + "node_id": "R_kgDOKxegYA", + "name": "demo-repository", + "full_name": "ant-xuexiao/demo-repository", + "private": true, + "owner": { + "login": "ant-xuexiao", + "id": 151921220, + "node_id": "O_kgDOCQ4iRA", + "avatar_url": "https://avatars.githubusercontent.com/u/151921220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ant-xuexiao", + "html_url": "https://github.com/ant-xuexiao", + "followers_url": "https://api.github.com/users/ant-xuexiao/followers", + "following_url": "https://api.github.com/users/ant-xuexiao/following{/other_user}", + "gists_url": "https://api.github.com/users/ant-xuexiao/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ant-xuexiao/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ant-xuexiao/subscriptions", + "organizations_url": "https://api.github.com/users/ant-xuexiao/orgs", + "repos_url": "https://api.github.com/users/ant-xuexiao/repos", + "events_url": "https://api.github.com/users/ant-xuexiao/events{/privacy}", + "received_events_url": "https://api.github.com/users/ant-xuexiao/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/ant-xuexiao/demo-repository", + "description": "A code repository designed to show the best GitHub has to offer.", + "fork": false, + "url": "https://api.github.com/repos/ant-xuexiao/demo-repository", + "forks_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/forks", + "keys_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/teams", + "hooks_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/hooks", + "issue_events_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/events{/number}", + "events_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/events", + "assignees_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/assignees{/user}", + "branches_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/branches{/branch}", + "tags_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/tags", + "blobs_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/statuses/{sha}", + "languages_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/languages", + "stargazers_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/stargazers", + "contributors_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/contributors", + "subscribers_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/subscribers", + "subscription_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/subscription", + "commits_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/contents/{+path}", + "compare_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/merges", + "archive_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/downloads", + "issues_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues{/number}", + "pulls_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls{/number}", + "milestones_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/milestones{/number}", + "notifications_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/labels{/name}", + "releases_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/releases{/id}", + "deployments_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/deployments", + "created_at": "2023-11-24T11:20:30Z", + "updated_at": "2023-11-24T11:20:35Z", + "pushed_at": "2024-04-14T11:05:29Z", + "git_url": "git://github.com/ant-xuexiao/demo-repository.git", + "ssh_url": "git@github.com:ant-xuexiao/demo-repository.git", + "clone_url": "https://github.com/ant-xuexiao/demo-repository.git", + "svn_url": "https://github.com/ant-xuexiao/demo-repository", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": "HTML", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 1, + "license": null, + "allow_forking": false, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "private", + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls/4" + }, + "html": { + "href": "https://github.com/ant-xuexiao/demo-repository/pull/4" + }, + "issue": { + "href": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/4" + }, + "comments": { + "href": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/4/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls/4/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls/4/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/ant-xuexiao/demo-repository/statuses/bf881bd623bd5a27839576e68c192d4b6daaeabb" + } + }, + "author_association": "NONE", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": null, + "rebaseable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 1, + "additions": 2, + "deletions": 0, + "changed_files": 1 + }, + "repository": { + "id": 722968672, + "node_id": "R_kgDOKxegYA", + "name": "demo-repository", + "full_name": "ant-xuexiao/demo-repository", + "private": true, + "owner": { + "login": "ant-xuexiao", + "id": 151921220, + "node_id": "O_kgDOCQ4iRA", + "avatar_url": "https://avatars.githubusercontent.com/u/151921220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ant-xuexiao", + "html_url": "https://github.com/ant-xuexiao", + "followers_url": "https://api.github.com/users/ant-xuexiao/followers", + "following_url": "https://api.github.com/users/ant-xuexiao/following{/other_user}", + "gists_url": "https://api.github.com/users/ant-xuexiao/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ant-xuexiao/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ant-xuexiao/subscriptions", + "organizations_url": "https://api.github.com/users/ant-xuexiao/orgs", + "repos_url": "https://api.github.com/users/ant-xuexiao/repos", + "events_url": "https://api.github.com/users/ant-xuexiao/events{/privacy}", + "received_events_url": "https://api.github.com/users/ant-xuexiao/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/ant-xuexiao/demo-repository", + "description": "A code repository designed to show the best GitHub has to offer.", + "fork": false, + "url": "https://api.github.com/repos/ant-xuexiao/demo-repository", + "forks_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/forks", + "keys_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/teams", + "hooks_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/hooks", + "issue_events_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/events{/number}", + "events_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/events", + "assignees_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/assignees{/user}", + "branches_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/branches{/branch}", + "tags_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/tags", + "blobs_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/statuses/{sha}", + "languages_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/languages", + "stargazers_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/stargazers", + "contributors_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/contributors", + "subscribers_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/subscribers", + "subscription_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/subscription", + "commits_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/contents/{+path}", + "compare_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/merges", + "archive_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/downloads", + "issues_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/issues{/number}", + "pulls_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/pulls{/number}", + "milestones_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/milestones{/number}", + "notifications_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/labels{/name}", + "releases_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/releases{/id}", + "deployments_url": "https://api.github.com/repos/ant-xuexiao/demo-repository/deployments", + "created_at": "2023-11-24T11:20:30Z", + "updated_at": "2023-11-24T11:20:35Z", + "pushed_at": "2024-04-14T11:05:29Z", + "git_url": "git://github.com/ant-xuexiao/demo-repository.git", + "ssh_url": "git@github.com:ant-xuexiao/demo-repository.git", + "clone_url": "https://github.com/ant-xuexiao/demo-repository.git", + "svn_url": "https://github.com/ant-xuexiao/demo-repository", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": "HTML", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 1, + "license": null, + "allow_forking": false, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "private", + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main", + "custom_properties": {} + }, + "organization": { + "login": "ant-xuexiao", + "id": 151921220, + "node_id": "O_kgDOCQ4iRA", + "url": "https://api.github.com/orgs/ant-xuexiao", + "repos_url": "https://api.github.com/orgs/ant-xuexiao/repos", + "events_url": "https://api.github.com/orgs/ant-xuexiao/events", + "hooks_url": "https://api.github.com/orgs/ant-xuexiao/hooks", + "issues_url": "https://api.github.com/orgs/ant-xuexiao/issues", + "members_url": "https://api.github.com/orgs/ant-xuexiao/members{/member}", + "public_members_url": "https://api.github.com/orgs/ant-xuexiao/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/151921220?v=4", + "description": null + }, + "sender": { + "login": "RaoHai", + "id": 566097, + "node_id": "MDQ6VXNlcjU2NjA5Nw==", + "avatar_url": "https://avatars.githubusercontent.com/u/566097?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/RaoHai", + "html_url": "https://github.com/RaoHai", + "followers_url": "https://api.github.com/users/RaoHai/followers", + "following_url": "https://api.github.com/users/RaoHai/following{/other_user}", + "gists_url": "https://api.github.com/users/RaoHai/gists{/gist_id}", + "starred_url": "https://api.github.com/users/RaoHai/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/RaoHai/subscriptions", + "organizations_url": "https://api.github.com/users/RaoHai/orgs", + "repos_url": "https://api.github.com/users/RaoHai/repos", + "events_url": "https://api.github.com/users/RaoHai/events{/privacy}", + "received_events_url": "https://api.github.com/users/RaoHai/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 49588447, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDk1ODg0NDc=" + } +} \ No newline at end of file diff --git a/tests/github/pull_request_test.py b/tests/github/pull_request_test.py new file mode 100644 index 00000000..d60b0e69 --- /dev/null +++ b/tests/github/pull_request_test.py @@ -0,0 +1,13 @@ +import pytest +import json +import os + +from server.event_handler.pull_request import PullRequestEventHandler + +def test_event_handler(): + filepath = os.path.join(os.path.dirname(__file__), 'pull_request_event.json') + with open(filepath) as ev: + event = json.load(ev) + handler = PullRequestEventHandler(payload = event, access_token="123") + result = handler.execute() + assert result["success"] == True