You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2025/07/09 18:54:32 UTC

(tooling-trusted-release) branch main updated: Add API endpoints to list release files and revisions

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 cf6c3a0  Add API endpoints to list release files and revisions
cf6c3a0 is described below

commit cf6c3a0c78c3f7a31293c26d8122c3ba8246f99d
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Wed Jul 9 19:54:05 2025 +0100

    Add API endpoints to list release files and revisions
---
 atr/blueprints/api/api.py | 46 ++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 40 insertions(+), 6 deletions(-)

diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 728e8c2..fa2c3ce 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -70,8 +70,8 @@ class Task(Pagination):
 class FileUploadRequest(schema.Strict):
     project_name: str
     version: str
+    rel_path: str
     content: str
-    file_name: str | None = None
 
 
 class PATJWTRequest(schema.Strict):
@@ -165,6 +165,27 @@ async def committees_name_projects(name: str) -> tuple[list[Mapping], int]:
         return [project.model_dump() for project in committee.projects], 200
 
 
+@api.BLUEPRINT.route("/list/<project>/<version>")
+@api.BLUEPRINT.route("/list/<project>/<version>/<revision>")
+@quart_schema.validate_response(dict[str, list[str]], 200)
+async def list_project_version(
+    project: str, version: str, revision: str | None = None
+) -> tuple[dict[str, list[str]], int]:
+    async with db.session() as data:
+        release_name = models.release_name(project, version)
+        release = await data.release(name=release_name).demand(exceptions.NotFound())
+        if revision is None:
+            dir_path = util.release_directory(release)
+        else:
+            await data.revision(release_name=release_name, number=revision).demand(exceptions.NotFound())
+            dir_path = util.release_directory_version(release) / revision
+    if not (await aiofiles.os.path.isdir(dir_path)):
+        raise exceptions.NotFound("Files not found")
+    files: list[str] = [str(path) for path in [p async for p in util.paths_recursive(dir_path)]]
+    files.sort()
+    return {"rel_paths": files}, 200
+
+
 @api.BLUEPRINT.route("/jwt", methods=["POST"])
 async def pat_jwt_post() -> quart.Response:
     """Generate a JWT from a valid PAT."""
@@ -348,6 +369,19 @@ async def releases_project_version_revisions(project: str, version: str) -> tupl
         return [rev.model_dump() for rev in revisions], 200
 
 
+@api.BLUEPRINT.route("/revisions/<project>/<version>")
+@quart_schema.validate_response(dict[str, list[models.Revision]], 200)
+async def revisions_project_version(project: str, version: str) -> tuple[dict[str, list[models.Revision]], int]:
+    async with db.session() as data:
+        release_name = models.release_name(project, version)
+        await data.release(name=release_name).demand(exceptions.NotFound())
+        revisions = await data.revision(release_name=release_name).all()
+    if not isinstance(revisions, list):
+        revisions = list(revisions)
+    revisions.sort(key=lambda rev: rev.number)
+    return {"revisions": revisions}, 200
+
+
 # @api.BLUEPRINT.route("/secret")
 # @jwtoken.require
 # @quart_schema.security_scheme([{"BearerAuth": []}])
@@ -464,8 +498,8 @@ async def upload() -> tuple[Mapping, int]:
         if not (user.is_committee_member(project.committee, asf_uid) or user.is_admin(asf_uid)):
             raise exceptions.Forbidden("You do not have permission to upload to this project")
 
-    revision_row = await _upload_process_file(req, asf_uid)
-    return revision_row.model_dump(), 201
+    revision = await _upload_process_file(req, asf_uid)
+    return revision.model_dump(), 201
 
 
 @db.session_function
@@ -503,10 +537,10 @@ async def _payload_get() -> dict:
 
 async def _upload_process_file(req: FileUploadRequest, asf_uid: str) -> models.Revision:
     file_bytes = base64.b64decode(req.content, validate=True)
-    file_name = (req.file_name or "uploaded-file").lstrip("/")
-    description = f"Upload via API: {file_name}"
+    file_path = req.rel_path.lstrip("/")
+    description = f"Upload via API: {file_path}"
     async with revision.create_and_manage(req.project_name, req.version, asf_uid, description=description) as creating:
-        target_path = pathlib.Path(creating.interim_path) / file_name
+        target_path = pathlib.Path(creating.interim_path) / file_path
         await aiofiles.os.makedirs(target_path.parent, exist_ok=True)
         async with aiofiles.open(target_path, "wb") as f:
             await f.write(file_bytes)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org