From f0e365fd43cadc45c0814fed7f428ddee7a98787 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 1 Feb 2026 16:44:42 -0500 Subject: [PATCH] Add GET /api/v1/version endpoint for deploy verification Returns the git commit hash of the running code. CLAUDE.md updated to check this endpoint after pushing to confirm deployment is live. --- CLAUDE.md | 12 +++++++++++- remarkbox/api/__init__.py | 1 + remarkbox/api/remarkbox_client.py | 10 ++++++++++ remarkbox/api/views.py | 32 +++++++++++++++++++++++++++++++ remarkbox/tests/test_api_views.py | 11 +++++++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1af092f..3727921 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,7 +47,17 @@ tmux-hosts ## Deployment Status -Check GitLab pipeline status after pushing: +After pushing, check if the deploy is live by hitting the version endpoint: + +```bash +curl -s https://my.remarkbox.com/api/v1/version +# {"version": "5a10e15"} +``` + +Compare the returned commit hash against `git rev-parse --short HEAD` to confirm +the latest code is deployed. + +You can also check GitLab pipeline status: ```bash # Get pipeline status via API (replace PIPELINE_ID) diff --git a/remarkbox/api/__init__.py b/remarkbox/api/__init__.py index 48754db..0e5eca1 100644 --- a/remarkbox/api/__init__.py +++ b/remarkbox/api/__init__.py @@ -1,4 +1,5 @@ def includeme(config): + config.add_route("api-version", "/api/v1/version") config.add_route("api-threads-list", "/api/v1/threads") config.add_route("api-thread-detail", "/api/v1/threads/{node_id}") config.add_route("api-thread-replies", "/api/v1/threads/{node_id}/replies") diff --git a/remarkbox/api/remarkbox_client.py b/remarkbox/api/remarkbox_client.py index 2d2d62a..890ef6e 100644 --- a/remarkbox/api/remarkbox_client.py +++ b/remarkbox/api/remarkbox_client.py @@ -175,6 +175,16 @@ class RemarkboxClient: body = {"error": raw} raise RemarkboxError(e.code, body) + # ----- Version ----- + + def version(self): + """Get the deployed version (git commit hash). + + Returns: + dict with key: version + """ + return self._request("GET", "/api/v1/version") + # ----- Threads ----- def list_threads(self, namespace, page=1): diff --git a/remarkbox/api/views.py b/remarkbox/api/views.py index f957f0b..ff9bac6 100644 --- a/remarkbox/api/views.py +++ b/remarkbox/api/views.py @@ -1,5 +1,6 @@ import os import re +import subprocess from pyramid.response import Response from pyramid.view import view_config @@ -51,6 +52,37 @@ def get_param(request, key, default=None): return request.params.get(key, default) +def _get_git_commit(): + """Read the current git commit hash, once at import time.""" + try: + return subprocess.check_output( + ["git", "rev-parse", "--short", "HEAD"], + cwd=os.path.dirname(os.path.abspath(__file__)), + stderr=subprocess.DEVNULL, + ).decode().strip() + except Exception: + return "unknown" + + +_GIT_COMMIT = _get_git_commit() + + +# --------------------------------------------------------------------------- +# Version +# --------------------------------------------------------------------------- + + +@view_config( + route_name="api-version", + request_method="GET", + renderer="json", + require_csrf=False, +) +def api_version(request): + """Return the deployed version (git commit).""" + return {"version": _GIT_COMMIT} + + # --------------------------------------------------------------------------- # Threads # --------------------------------------------------------------------------- diff --git a/remarkbox/tests/test_api_views.py b/remarkbox/tests/test_api_views.py index 2b9e7b6..db7631d 100644 --- a/remarkbox/tests/test_api_views.py +++ b/remarkbox/tests/test_api_views.py @@ -851,6 +851,17 @@ class TestAPINamespaceOptOut(APIFunctionalTests): self.assertEqual(res.status_int, 403) +class TestAPIVersion(APIFunctionalTests): + """Test the version endpoint.""" + + def test_version_returns_commit(self): + res = self.testapp.get("/api/v1/version") + self.assertEqual(res.status_int, 200) + self.assertIn("version", res.json) + self.assertIsInstance(res.json["version"], str) + self.assertGreater(len(res.json["version"]), 0) + + class TestAPIClientDownload(APIFunctionalTests): """Test the client download endpoint."""