From cfc4e6e9afbcdc53e3130e281047c779b89476c9 Mon Sep 17 00:00:00 2001 From: iamprecieee Date: Tue, 18 Feb 2025 12:57:03 +0100 Subject: [PATCH] feat(core): configure router for forwarding github commits to telex - Add github_webhook route to receive and forward validated commits data to a telex channel - Create telex send function to handle async sending of payload using curl subprocess - Include router config in main.py app - Update config.py and env example with variables for webhook url and curl command Also install missing pytest package --- .env.example | 4 +++- main.py | 8 +++++--- requirements.txt | 8 ++++++++ src/config/config.py | 9 ++++++--- src/routers/github.py | 37 +++++++++++++++++++++++++++++++++++++ src/routers/router.py | 6 ++++++ src/utils/telex_utils.py | 32 ++++++++++++++++++++++++++++++++ 7 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 src/utils/telex_utils.py diff --git a/.env.example b/.env.example index f54ec0b..cd2ba9a 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,6 @@ ALLOWED_ORIGINS=https://github.com,http://127.0.0.1:8000,http://0.0.0.0:8000,htt ALLOWED_HOSTS=github.com,127.0.0.1,0.0.0.0,localhost,ping.telex.im HOST=0.0.0.0 PORT=8000 -RELOAD_VALUE=True \ No newline at end of file +RELOAD_VALUE=True +TELEX_WEBHOOK_URL=https://ping.telex.im/v1/webhooks +CURL_COMMAND=curl \ No newline at end of file diff --git a/main.py b/main.py index af7a314..b20cf0c 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,13 @@ from fastapi import FastAPI -import uvicorn -from src.config.config import settings from src.config.middleware import middleware +from src.routers.router import webhook_router +from src.config.config import settings +import uvicorn app = FastAPI(docs_url="/", middleware=middleware) +app.include_router(webhook_router) if __name__ == "__main__": reload_value = settings.reload_value.lower() == "true" - uvicorn.run("main:app", host=settings.host, port=settings.port, reload=reload_value) \ No newline at end of file + uvicorn.run("main:app", host=settings.host, port=settings.port, reload=reload_value) diff --git a/requirements.txt b/requirements.txt index 5a44abc..aac14bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ annotated-types==0.7.0 anyio==4.8.0 +black==25.1.0 certifi==2025.1.31 click==8.1.8 dnspython==2.7.0 @@ -11,20 +12,27 @@ httpcore==1.0.7 httptools==0.6.4 httpx==0.28.1 idna==3.10 +iniconfig==2.0.0 itsdangerous==2.2.0 Jinja2==3.1.5 joblib==1.4.2 markdown-it-py==3.0.0 MarkupSafe==3.0.2 mdurl==0.1.2 +mypy-extensions==1.0.0 nltk==3.9.1 numpy==2.2.3 orjson==3.10.15 +packaging==24.2 +pathspec==0.12.1 +platformdirs==4.3.6 +pluggy==1.5.0 pydantic==2.10.6 pydantic-extra-types==2.10.2 pydantic-settings==2.7.1 pydantic_core==2.27.2 Pygments==2.19.1 +pytest==8.3.4 python-dotenv==1.0.1 python-multipart==0.0.20 PyYAML==6.0.2 diff --git a/src/config/config.py b/src/config/config.py index aef3256..5b4fef6 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -7,8 +7,10 @@ class Settings(BaseSettings): allowed_hosts: str host: str port: int - reload_value: str - + reload_value: str + telex_webhook_url: str + curl_command: str | None = "curl" # might require path/to/curl e.g. `/usr/bin/curl` + model_config = SettingsConfigDict(env_file=".env") @@ -16,4 +18,5 @@ class Settings(BaseSettings): def get_settings() -> Settings: return Settings() -settings = get_settings() \ No newline at end of file + +settings = get_settings() diff --git a/src/routers/github.py b/src/routers/github.py index e69de29..29e3c64 100644 --- a/src/routers/github.py +++ b/src/routers/github.py @@ -0,0 +1,37 @@ +from fastapi.routing import APIRouter +from ..core.models import GitHubPayload, TelexWebhookPayload +from ..config.config import settings +from ..utils.telex_utils import send_payload_to_telex +from fastapi.responses import JSONResponse +from fastapi import status, HTTPException +import json + + +router = APIRouter(prefix="/github") + + +@router.post("/{telex_channel_id}", status_code=status.HTTP_200_OK) +async def github_webhook(telex_channel_id: str, payload: GitHubPayload): + """Endpoint to receive GitHub webhook events and forward the commits to Telex""" + # Payload in format required by Telex channels' webhooks + telex_payload = TelexWebhookPayload( + event_name="pushed_commits", + message=str(payload.commits), + status="success", + username=payload.pusher["name"], + ).model_dump_json() + + # Telex channel webhook URL + telex_url = f"{settings.telex_webhook_url}/{telex_channel_id}" + + try: + # Send payload to Telex + response = await send_payload_to_telex(telex_payload, telex_url) + response_data = json.loads(response.decode().strip()) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Telex payload sending failed: {str(e)}", + ) + + return JSONResponse(content={"data": response_data}) diff --git a/src/routers/router.py b/src/routers/router.py index e69de29..dbcb6c4 100644 --- a/src/routers/router.py +++ b/src/routers/router.py @@ -0,0 +1,6 @@ +from fastapi.routing import APIRouter +from .github import router as github_router + + +webhook_router = APIRouter(prefix="/api/v1/webhook") +webhook_router.include_router(github_router) diff --git a/src/utils/telex_utils.py b/src/utils/telex_utils.py new file mode 100644 index 0000000..2828ad4 --- /dev/null +++ b/src/utils/telex_utils.py @@ -0,0 +1,32 @@ +import asyncio +from ..config.config import settings +from fastapi import HTTPException, status + + +async def send_payload_to_telex(telex_payload: str, telex_url: str): + """Sends payload through an asynchronous curl subprocess""" + curl_command = [ + settings.curl_command, + "-X", + "POST", + telex_url, + "-H", + "Accept: application/json", + "-H", + "Content-Type: application/json", + "-d", + telex_payload, + ] + process = await asyncio.create_subprocess_exec( + *curl_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + + # Check for exit on error + if process.returncode != 0: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Subprocess exception: {stderr.decode().strip()}", + ) + + return stdout