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.
This commit is contained in:
russell@unturf.com 2026-02-01 16:44:42 -05:00
parent 5a10e155bd
commit f0e365fd43
5 changed files with 65 additions and 1 deletions

View file

@ -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)

View file

@ -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")

View file

@ -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):

View file

@ -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
# ---------------------------------------------------------------------------

View file

@ -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."""