From 33e8fe931568a3339669fd2e0a146f40346e9df6 Mon Sep 17 00:00:00 2001 From: Space-Banane Date: Fri, 22 May 2026 22:28:11 +0200 Subject: [PATCH] feat. Mini UI to hide ugly 404s --- AGENTS.md | 2 +- TODO.md | 2 +- src/gitea_codex_bot/main.py | 114 ++++++++++++++++++++++++++++++++++++ tests/test_main_pages.py | 40 +++++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 tests/test_main_pages.py diff --git a/AGENTS.md b/AGENTS.md index 3d958c0..0e081fc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -98,7 +98,7 @@ pytest ``` Docker compose: - +> Locally only run this, is pre setup to use the dev compose file. ```bash docker compose up --build -f docker-compose.dev.yml ``` diff --git a/TODO.md b/TODO.md index a059dee..85bf1bb 100644 --- a/TODO.md +++ b/TODO.md @@ -25,7 +25,7 @@ ### P2 (Nice to Have) - [x] `FEATURE`: Add a note line at the end of comments to show model tokens used and such. -- [ ] `FEATURE`: Little static tailwind cdn styled page for any http endpoint that just shows what this is, incase this gets discovered by some random lad. Other routes than "/" should return a 404 with if a browser accessed it a again, tailwind cdn themed 404 page. Both should be nicely designed and minimalistic. +- [x] `FEATURE`: Little static tailwind cdn styled page for any http endpoint that just shows what this is, incase this gets discovered by some random lad. Other routes than "/" should return a 404 with if a browser accessed it a again, tailwind cdn themed 404 page. Both should be nicely designed and minimalistic. - [ ] `FEATURE`: Apply `.codex-review.yml` `review.default_mode` when `@codex review` is issued without explicit mode. - [ ] `FEATURE`: Add per-repo command policy in `.codex-review.yml` for enabling/disabling `review`, `fix`, `explain`, and `rerun` independently. - [ ] `TEST`: Add structured log redaction tests to ensure PAT/keys never appear in logs/comments. diff --git a/src/gitea_codex_bot/main.py b/src/gitea_codex_bot/main.py index 83402f7..dbc9186 100644 --- a/src/gitea_codex_bot/main.py +++ b/src/gitea_codex_bot/main.py @@ -8,6 +8,9 @@ from pathlib import Path from typing import Any from fastapi import Depends, FastAPI, Header, HTTPException, Request, status +from fastapi.exception_handlers import http_exception_handler +from fastapi.responses import HTMLResponse +from starlette.exceptions import HTTPException as StarletteHTTPException from sqlalchemy.orm import Session from gitea_codex_bot.config import Settings, get_settings @@ -134,6 +137,117 @@ async def lifespan(app: FastAPI): app = FastAPI(title="Gitea Codex Review Bot", lifespan=lifespan) +def _render_landing_page() -> str: + return """ + + + + + Gitea Codex Review Bot + + + +
+
+

Webhook Service

+

Gitea Codex Review Bot

+

This endpoint powers automated pull request review workflows for Gitea. It validates signed webhook events, queues review jobs, and posts structured feedback back to pull requests.

+
+ + Webhook: POST /webhook/gitea +
+
+
+ + + +""" + + +def _render_browser_404_page() -> str: + return """ + + + + + Not Found + + + +
+
+

Error 404

+

Page not found

+

This service exposes only a small set of routes. Head back to the home page for a quick overview.

+ Go to home +
+
+ +""" + + +@app.exception_handler(StarletteHTTPException) +async def custom_http_exception_handler(request: Request, exc: StarletteHTTPException): + if exc.status_code == status.HTTP_404_NOT_FOUND: + accept = request.headers.get("accept", "") + if "text/html" in accept.lower(): + return HTMLResponse(content=_render_browser_404_page(), status_code=status.HTTP_404_NOT_FOUND) + return await http_exception_handler(request, exc) + + +@app.get("/", response_class=HTMLResponse) +def root() -> str: + return _render_landing_page() + + @app.get("/healthz") def healthz(settings: Settings = Depends(get_settings)) -> dict[str, str]: _ = settings.gitea_base_url diff --git a/tests/test_main_pages.py b/tests/test_main_pages.py new file mode 100644 index 0000000..fd6443a --- /dev/null +++ b/tests/test_main_pages.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from fastapi.testclient import TestClient + +from gitea_codex_bot.main import app + + +def test_root_returns_tailwind_landing_page() -> None: + client = TestClient(app) + + response = client.get("/") + + assert response.status_code == 200 + assert response.headers["content-type"].startswith("text/html") + assert "Gitea Codex Review Bot" in response.text + assert "cdn.tailwindcss.com" in response.text + assert 'id="health-button"' in response.text + assert 'id="health-modal"' in response.text + assert 'fetch("/healthz"' in response.text + + +def test_404_returns_tailwind_page_for_browser_requests() -> None: + client = TestClient(app) + + response = client.get("/missing", headers={"Accept": "text/html"}) + + assert response.status_code == 404 + assert response.headers["content-type"].startswith("text/html") + assert "Error 404" in response.text + assert "cdn.tailwindcss.com" in response.text + + +def test_404_returns_json_for_non_browser_requests() -> None: + client = TestClient(app) + + response = client.get("/missing", headers={"Accept": "application/json"}) + + assert response.status_code == 404 + assert response.headers["content-type"].startswith("application/json") + assert response.json() == {"detail": "Not Found"}