This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git


The following commit(s) were added to refs/heads/main by this push:
     new 41b3ae8  Add an API endpoint to create a new release
41b3ae8 is described below

commit 41b3ae81f52caf3a3610f9e930b1c6d1d665d725
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Jul 3 21:50:58 2025 +0100

    Add an API endpoint to create a new release
---
 atr/blueprints/api/api.py | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index e5f869c..f64525d 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -20,6 +20,7 @@ import datetime
 import hashlib
 from collections.abc import Mapping
 
+import asfquart.base as base
 import quart
 import quart_schema
 import sqlalchemy
@@ -31,6 +32,8 @@ import atr.blueprints.api as api
 import atr.db as db
 import atr.db.models as models
 import atr.jwtoken as jwtoken
+import atr.routes as routes
+import atr.routes.start as start
 import atr.schema as schema
 
 # FIXME: we need to return the dumped model instead of the actual pydantic 
class
@@ -60,6 +63,11 @@ class PATJWTRequest(schema.Strict):
     pat: str
 
 
+class ReleaseCreateRequest(schema.Strict):
+    project_name: str
+    version: str
+
+
 # We implicitly have /api/openapi.json
 
 
@@ -211,6 +219,32 @@ async def releases(query_args: Releases) -> quart.Response:
         return quart.jsonify(result)
 
 
[email protected]("/releases/create", methods=["POST"])
[email protected]
+@quart_schema.security_scheme([{"BearerAuth": []}])
+@quart_schema.validate_response(models.Release, 201)
+async def releases_create() -> tuple[Mapping, int]:
+    """Create a new release draft for a project via POSTed JSON."""
+
+    payload = await quart.request.get_json(force=True, silent=False)
+    if not isinstance(payload, dict):
+        raise exceptions.BadRequest("Invalid JSON")
+
+    request_data = ReleaseCreateRequest.model_validate(payload)
+    asf_uid = _jwt_asf_uid()
+
+    try:
+        release, _project = await start.create_release_draft(
+            project_name=request_data.project_name,
+            version=request_data.version,
+            asf_uid=asf_uid,
+        )
+    except routes.FlashError as exc:
+        raise exceptions.BadRequest(str(exc))
+
+    return release.model_dump(), 201
+
+
 @api.BLUEPRINT.route("/releases/<project>/<version>")
 @quart_schema.validate_response(models.Release, 200)
 async def releases_project_version(project: str, version: str) -> 
tuple[Mapping, int]:
@@ -303,6 +337,14 @@ async def _get_pat(data: db.Session, uid: str, token_hash: 
str) -> models.Person
     )
 
 
+def _jwt_asf_uid() -> str:
+    claims = getattr(quart.g, "jwt_claims", {})
+    asf_uid = claims.get("sub")
+    if not isinstance(asf_uid, str):
+        raise base.ASFQuartException("Invalid token subject", errorcode=401)
+    return asf_uid
+
+
 def _pagination_args_validate(query_args: Pagination) -> None:
     # Users could request any amount using limit=N with arbitrarily high N
     # We therefore limit the maximum limit to 1000


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to