{{ scan.package_name }}
{{ scan.package_name }}
{{ scan.package_version }}
{{ scan.ecosystem }}
{{ scan.repository }}
diff --git a/guarddog_nexus/api/packages.py b/guarddog_nexus/api/packages.py index 7b61b10..44494fc 100644 --- a/guarddog_nexus/api/packages.py +++ b/guarddog_nexus/api/packages.py @@ -2,6 +2,7 @@ import csv import io +from urllib.parse import unquote from fastapi import APIRouter, Depends, Query, Response from sqlalchemy import select @@ -108,17 +109,20 @@ async def export_packages_csv( ) -@router.get("/{name}/{version}") +@router.get("/{name:path}") async def get_package( name: str, - version: str, session: AsyncSession = Depends(get_session), ): + parts = name.rsplit("/", 1) + pkg_name = unquote(parts[0]) + pkg_version = unquote(parts[1]) if len(parts) == 2 else "" + scans = ( ( await session.execute( select(Scan) - .where(Scan.package_name == name, Scan.package_version == version) + .where(Scan.package_name == pkg_name, Scan.package_version == pkg_version) .order_by(Scan.started_at.desc()) ) ) diff --git a/guarddog_nexus/web/routes.py b/guarddog_nexus/web/routes.py index 1d4593f..d1960a0 100644 --- a/guarddog_nexus/web/routes.py +++ b/guarddog_nexus/web/routes.py @@ -1,5 +1,7 @@ """Web UI routes — Jinja2 + htmx pages.""" +from urllib.parse import unquote + from fastapi import APIRouter, Depends, Request from fastapi.responses import HTMLResponse from jinja2 import Environment, PackageLoader, select_autoescape @@ -154,20 +156,25 @@ async def packages_list( ) -@router.get("/packages/{name}/{version}", response_class=HTMLResponse) +@router.get("/packages/{name:path}", response_class=HTMLResponse) async def package_detail( name: str, - version: str, request: Request, session: AsyncSession = Depends(get_session), ): + # name:path captures the entire path after /packages/ + # e.g. "eviltest/0.1.0" or "github.com/attacker/evilmodule/v0.1.0" + parts = name.rsplit("/", 1) + pkg_name = unquote(parts[0]) + pkg_version = unquote(parts[1]) if len(parts) == 2 else "" + from sqlalchemy.orm import selectinload scans = ( ( await session.execute( select(Scan) - .where(Scan.package_name == name, Scan.package_version == version) + .where(Scan.package_name == pkg_name, Scan.package_version == pkg_version) .options(selectinload(Scan.findings)) .order_by(Scan.started_at.desc()) ) @@ -185,8 +192,8 @@ async def package_detail( return _render( "package_detail.html", - pkg_name=name, - pkg_version=version, + pkg_name=pkg_name, + pkg_version=pkg_version, scans=scans, findings=all_findings, request=request, diff --git a/guarddog_nexus/web/templates/_packages_table.html b/guarddog_nexus/web/templates/_packages_table.html index af91d22..fb79580 100644 --- a/guarddog_nexus/web/templates/_packages_table.html +++ b/guarddog_nexus/web/templates/_packages_table.html @@ -21,7 +21,7 @@
{% for p in packages %}