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-atr-experiments.git


The following commit(s) were added to refs/heads/main by this push:
     new 46e5154  Refactor routes and other code, and add a site navigation page
46e5154 is described below

commit 46e51549c773e349baec6aa1851cf59b9147d410
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Feb 13 19:30:01 2025 +0200

    Refactor routes and other code, and add a site navigation page
---
 atr/routes.py                                   | 147 ++++++++++------
 atr/server.py                                   |  24 +--
 atr/templates/data-browser.html                 |   2 +-
 atr/templates/pages.html                        | 213 ++++++++++++++++++++++++
 atr/templates/{root.html => pmc-directory.html} |   0
 atr/templates/user-uploads.html                 |   2 +-
 pyproject.toml                                  |   2 +-
 7 files changed, 315 insertions(+), 75 deletions(-)

diff --git a/atr/routes.py b/atr/routes.py
index d9a482c..56ba7b8 100644
--- a/atr/routes.py
+++ b/atr/routes.py
@@ -64,30 +64,26 @@ def compute_sha512(file_path: Path) -> str:
     return sha512.hexdigest()
 
 
-async def save_file_by_hash(file, base_dir: Path) -> Tuple[Path, str]:
-    """
-    Save a file using its SHA3-256 hash as the filename.
-    Returns the path where the file was saved and its hash.
-    """
-    # FileStorage.read() returns bytes directly, no need to await
-    data = file.read()
-    file_hash = compute_sha3_256(data)
-
-    # Create path with hash as filename
-    path = base_dir / file_hash
-    path.parent.mkdir(parents=True, exist_ok=True)
-
-    # Only write if file doesn't exist
-    # If it does exist, it'll be the same content anyway
-    if not path.exists():
-        path.write_bytes(data)
-
-    return path, file_hash
[email protected]("/")
+async def root() -> str:
+    "Main PMC directory page."
+    return """\
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>ATR</title>
+</head>
+<body>
+    <h1>Apache Trusted Releases</h1>
+</body>
+</html>
+"""
 
 
 @APP.route("/add-release-candidate", methods=["GET", "POST"])
 @require(R.committer)
-async def add_release_candidate() -> str:
+async def root_add_release_candidate() -> str:
     "Add a release candidate to the database."
     session = await session_read()
     if session is None:
@@ -178,10 +174,10 @@ async def add_release_candidate() -> str:
     )
 
 
[email protected]("/admin/data-browser")
[email protected]("/admin/data-browser/<model>")
[email protected]("/admin/database")
[email protected]("/admin/database/<model>")
 @require(R.committer)
-async def admin_data_browser(model: str = "PMC") -> str:
+async def root_admin_database(model: str = "PMC") -> str:
     "Browse all records in the database."
     session = await session_read()
     if session is None:
@@ -231,7 +227,8 @@ async def admin_data_browser(model: str = "PMC") -> str:
 
 
 @APP.route("/admin/update-pmcs", methods=["GET", "POST"])
-async def admin_update_pmcs() -> str:
+@require(R.committer)
+async def root_admin_update_pmcs() -> str:
     "Update PMCs from remote, authoritative committee-info.json."
     # Check authentication
     session = await session_read()
@@ -307,8 +304,42 @@ async def admin_update_pmcs() -> str:
     return await render_template("update-pmcs.html")
 
 
[email protected]("/database/debug")
+async def root_database_debug() -> str:
+    """Debug information about the database."""
+    with Session(current_app.config["engine"]) as session:
+        statement = select(PMC)
+        pmcs = session.exec(statement).all()
+        return f"Database using {current_app.config['DATA_MODELS_FILE']} has 
{len(pmcs)} PMCs"
+
+
[email protected]("/pages")
+async def root_pages() -> str:
+    "List all pages on the website."
+    return await render_template("pages.html")
+
+
[email protected]("/pmc/<project_name>")
+async def root_pmc_arg(project_name: str) -> dict:
+    "Get a specific PMC by project name."
+    with Session(current_app.config["engine"]) as session:
+        statement = select(PMC).where(PMC.project_name == project_name)
+        pmc = session.exec(statement).first()
+
+        if not pmc:
+            raise ASFQuartException("PMC not found", errorcode=404)
+
+        return {
+            "id": pmc.id,
+            "project_name": pmc.project_name,
+            "pmc_members": pmc.pmc_members,
+            "committers": pmc.committers,
+            "release_managers": pmc.release_managers,
+        }
+
+
 @APP.route("/pmc/create/<project_name>")
-async def pmc_create_arg(project_name: str) -> dict:
+async def root_pmc_create_arg(project_name: str) -> dict:
     "Create a new PMC with some sample data."
     pmc = PMC(
         project_name=project_name,
@@ -338,8 +369,18 @@ async def pmc_create_arg(project_name: str) -> dict:
         }
 
 
[email protected]("/pmc/directory")
+async def root_pmc_directory() -> str:
+    "Main PMC directory page."
+    with Session(current_app.config["engine"]) as session:
+        # Get all PMCs and their latest releases
+        statement = select(PMC)
+        pmcs = session.exec(statement).all()
+        return await render_template("pmc-directory.html", pmcs=pmcs)
+
+
 @APP.route("/pmc/list")
-async def pmc_list() -> List[dict]:
+async def root_pmc_list() -> List[dict]:
     "List all PMCs in the database."
     with Session(current_app.config["engine"]) as session:
         statement = select(PMC)
@@ -357,38 +398,15 @@ async def pmc_list() -> List[dict]:
         ]
 
 
[email protected]("/pmc/<project_name>")
-async def pmc_arg(project_name: str) -> dict:
-    "Get a specific PMC by project name."
-    with Session(current_app.config["engine"]) as session:
-        statement = select(PMC).where(PMC.project_name == project_name)
-        pmc = session.exec(statement).first()
-
-        if not pmc:
-            raise ASFQuartException("PMC not found", errorcode=404)
-
-        return {
-            "id": pmc.id,
-            "project_name": pmc.project_name,
-            "pmc_members": pmc.pmc_members,
-            "committers": pmc.committers,
-            "release_managers": pmc.release_managers,
-        }
-
-
[email protected]("/")
-async def root() -> str:
-    "Main PMC directory page."
-    with Session(current_app.config["engine"]) as session:
-        # Get all PMCs and their latest releases
-        statement = select(PMC)
-        pmcs = session.exec(statement).all()
-        return await render_template("root.html", pmcs=pmcs)
[email protected]("/secret")
+@require(R.committer)
+async def root_secret() -> str:
+    return "Secret stuff!"
 
 
 @APP.route("/user/uploads")
 @require(R.committer)
-async def user_uploads() -> str:
+async def root_user_uploads() -> str:
     "Show all release candidates uploaded by the current user."
     session = await session_read()
     if session is None:
@@ -410,3 +428,24 @@ async def user_uploads() -> str:
                 user_releases.append(r)
 
         return await render_template("user-uploads.html", 
releases=user_releases)
+
+
+async def save_file_by_hash(file, base_dir: Path) -> Tuple[Path, str]:
+    """
+    Save a file using its SHA3-256 hash as the filename.
+    Returns the path where the file was saved and its hash.
+    """
+    # FileStorage.read() returns bytes directly, no need to await
+    data = file.read()
+    file_hash = compute_sha3_256(data)
+
+    # Create path with hash as filename
+    path = base_dir / file_hash
+    path.parent.mkdir(parents=True, exist_ok=True)
+
+    # Only write if file doesn't exist
+    # If it does exist, it'll be the same content anyway
+    if not path.exists():
+        path.write_bytes(data)
+
+    return path, file_hash
diff --git a/atr/server.py b/atr/server.py
index 7282064..c8305e0 100644
--- a/atr/server.py
+++ b/atr/server.py
@@ -20,13 +20,12 @@
 import os
 
 import asfquart
-from asfquart.auth import Requirements as R, require
 from asfquart.base import QuartApp
-from sqlmodel import Session, SQLModel, create_engine, select
+from sqlmodel import SQLModel, create_engine
 from alembic import command
 from alembic.config import Config
 
-from .models import PMC, __file__ as data_models_file
+from .models import __file__ as data_models_file
 
 
 def register_routes() -> str:
@@ -42,7 +41,7 @@ def create_app() -> QuartApp:
     app = asfquart.construct(__name__)
 
     @app.before_serving
-    async def create_database():
+    async def create_database() -> None:
         # Get the project root directory (where alembic.ini is)
         project_root = 
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
@@ -57,6 +56,7 @@ def create_app() -> QuartApp:
         release_storage = os.path.join(state_dir, "releases")
         os.makedirs(release_storage, exist_ok=True)
         app.config["RELEASE_STORAGE_DIR"] = release_storage
+        app.config["DATA_MODELS_FILE"] = data_models_file
 
         sqlite_url = "sqlite:///./atr.db"
         engine = create_engine(
@@ -91,23 +91,11 @@ def create_app() -> QuartApp:
 
         # Create any tables that might be missing
         SQLModel.metadata.create_all(engine)
-        app.config["engine"] = engine
-
-    @app.route("/secret")
-    @require(R.committer)
-    async def secret() -> str:
-        return "Secret stuff!"
 
-    @app.get("/database/debug")
-    async def database_debug() -> str:
-        """Debug information about the database."""
-        with Session(app.config["engine"]) as session:
-            statement = select(PMC)
-            pmcs = session.exec(statement).all()
-            return f"Database using {data_models_file} has {len(pmcs)} PMCs"
+        app.config["engine"] = engine
 
     @app.after_serving
-    async def shutdown():
+    async def shutdown() -> None:
         app.background_tasks.clear()
 
     register_routes()
diff --git a/atr/templates/data-browser.html b/atr/templates/data-browser.html
index 9f0a447..3281b48 100644
--- a/atr/templates/data-browser.html
+++ b/atr/templates/data-browser.html
@@ -65,7 +65,7 @@
 
     <div class="model-nav">
       {% for model_name in models %}
-        <a href="{{ url_for('admin_data_browser', model=model_name) }}"
+        <a href="{{ url_for('root_admin_database', model=model_name) }}"
            {% if model == model_name %}class="active"{% endif %}>{{ model_name 
}}</a>
       {% endfor %}
     </div>
diff --git a/atr/templates/pages.html b/atr/templates/pages.html
new file mode 100644
index 0000000..1d56336
--- /dev/null
+++ b/atr/templates/pages.html
@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+    <meta name="description" content="List of all pages and endpoints in ATR." 
/>
+    <title>ATR | Pages</title>
+    <link rel="stylesheet" href="{{ url_for('static', filename='root.css') }}" 
/>
+    <style>
+        .endpoint-list {
+            margin: 2rem 0;
+        }
+
+        .endpoint-group {
+            margin-bottom: 2rem;
+        }
+
+        .endpoint {
+            border: 1px solid #ddd;
+            padding: 1rem;
+            margin-bottom: 1rem;
+            border-radius: 4px;
+        }
+
+        .endpoint h3 {
+            margin: 0 0 0.5rem 0;
+        }
+
+        .endpoint-meta {
+            color: #666;
+            font-size: 0.9em;
+            margin-bottom: 0.5rem;
+        }
+
+        .endpoint-description {
+            margin-bottom: 0.5rem;
+        }
+
+        .access-requirement {
+            display: inline-block;
+            padding: 0.25rem 0.5rem;
+            border-radius: 2px;
+            font-size: 0.8em;
+            background: #f5f5f5;
+        }
+
+        .access-requirement.committer {
+            background: #e6f3ff;
+            border: 1px solid #cce5ff;
+        }
+
+        .access-requirement.admin {
+            background: #ffeeba;
+            border: 1px solid #f5d88c;
+        }
+
+        .access-requirement.public {
+            background: #e6ffe6;
+            border: 1px solid #ccebcc;
+        }
+    </style>
+  </head>
+  <body>
+    <h1>ATR Pages</h1>
+    <p class="intro">A complete list of all pages and endpoints available in 
ATR.</p>
+
+    <div class="endpoint-list">
+      <div class="endpoint-group">
+        <h2>Main Pages</h2>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root') }}">/</a>
+          </h3>
+          <div class="endpoint-description">Simple welcome page with Apache 
Trusted Releases title.</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement public">Public</span>
+          </div>
+        </div>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root_pages') }}">/pages</a>
+          </h3>
+          <div class="endpoint-description">List of all pages on the website 
(this page).</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement public">Public</span>
+          </div>
+        </div>
+      </div>
+
+      <div class="endpoint-group">
+        <h2>PMC Management</h2>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root_pmc_directory') }}">/pmc/directory</a>
+          </h3>
+          <div class="endpoint-description">Main PMC directory page with all 
PMCs and their latest releases.</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement public">Public</span>
+          </div>
+        </div>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root_pmc_list') }}">/pmc/list</a>
+          </h3>
+          <div class="endpoint-description">List all PMCs in the database 
(JSON format).</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement public">Public</span>
+          </div>
+        </div>
+
+        <div class="endpoint">
+          <h3>/pmc/&lt;project_name&gt;</h3>
+          <div class="endpoint-description">Get details for a specific PMC 
(JSON format).</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement public">Public</span>
+          </div>
+        </div>
+
+        <div class="endpoint">
+          <h3>/pmc/create/&lt;project_name&gt;</h3>
+          <div class="endpoint-description">Create a new PMC with sample data 
(JSON format).</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement public">Public</span>
+          </div>
+        </div>
+      </div>
+
+      <div class="endpoint-group">
+        <h2>Release Management</h2>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root_add_release_candidate') 
}}">/add-release-candidate</a>
+          </h3>
+          <div class="endpoint-description">Add a release candidate to the 
database.</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement committer">Committer</span>
+            <br />
+            Additional requirement: Must be PMC member of the target project
+          </div>
+        </div>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root_user_uploads') }}">/user/uploads</a>
+          </h3>
+          <div class="endpoint-description">Show all release candidates 
uploaded by the current user.</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement committer">Committer</span>
+          </div>
+        </div>
+      </div>
+
+      <div class="endpoint-group">
+        <h2>Administration</h2>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root_admin_database') }}">/admin/database</a>
+          </h3>
+          <div class="endpoint-description">Browse all records in the 
database.</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement committer">Committer</span>
+            <span class="access-requirement admin">Admin</span>
+            <br />
+            Additional requirement: Must be in ALLOWED_USERS list
+          </div>
+        </div>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root_admin_update_pmcs') 
}}">/admin/update-pmcs</a>
+          </h3>
+          <div class="endpoint-description">Update PMCs from remote, 
authoritative committee-info.json.</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement committer">Committer</span>
+            <span class="access-requirement admin">Admin</span>
+            <br />
+            Additional requirement: Must be in ALLOWED_USERS list
+          </div>
+        </div>
+      </div>
+
+      <div class="endpoint-group">
+        <h2>Debug/Testing</h2>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root_secret') }}">/secret</a>
+          </h3>
+          <div class="endpoint-description">Test endpoint for 
authentication.</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement committer">Committer</span>
+          </div>
+        </div>
+
+        <div class="endpoint">
+          <h3>
+            <a href="{{ url_for('root_database_debug') }}">/database/debug</a>
+          </h3>
+          <div class="endpoint-description">Debug information about the 
database.</div>
+          <div class="endpoint-meta">
+            Access: <span class="access-requirement public">Public</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/atr/templates/root.html b/atr/templates/pmc-directory.html
similarity index 100%
rename from atr/templates/root.html
rename to atr/templates/pmc-directory.html
diff --git a/atr/templates/user-uploads.html b/atr/templates/user-uploads.html
index 3a61d39..c4dd822 100644
--- a/atr/templates/user-uploads.html
+++ b/atr/templates/user-uploads.html
@@ -80,7 +80,7 @@
     {% endif %}
 
     <p>
-      <a href="{{ url_for('add_release_candidate') }}">Upload a new release 
candidate</a>
+      <a href="{{ url_for('root_add_release_candidate') }}">Upload a new 
release candidate</a>
     </p>
   </body>
 </html>
diff --git a/pyproject.toml b/pyproject.toml
index e5c1d11..f8ca0ee 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -99,5 +99,5 @@ format_js = true
 max_line_length = 120
 use_gitignore = true
 preserve_blank_lines = true
-ignore = "H006,H031"  # Ignore img alt and meta description warnings
+ignore = "H006,H031"
 include = "atr/templates"


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

Reply via email to