diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py
index 40322785..4426d0be 100644
--- a/aurweb/routers/packages.py
+++ b/aurweb/routers/packages.py
@@ -940,3 +940,40 @@ async def pkgbase_disown_post(request: Request, name: str,
disown_pkgbase(pkgbase, request.user)
return RedirectResponse(f"/pkgbase/{name}",
status_code=int(HTTPStatus.SEE_OTHER))
+
+
+@router.get("/pkgbase/{name}/delete")
+@auth_required(True)
+async def pkgbase_delete_get(request: Request, name: str):
+ if not request.user.has_credential("CRED_PKGBASE_DELETE"):
+ return RedirectResponse(f"/pkgbase/{name}",
+ status_code=int(HTTPStatus.SEE_OTHER))
+
+ context = make_context(request, "Package Deletion")
+ context["pkgbase"] = get_pkg_or_base(name, PackageBase)
+ return render_template(request, "packages/delete.html", context)
+
+
+@router.post("/pkgbase/{name}/delete")
+@auth_required(True)
+async def pkgbase_delete_post(request: Request, name: str,
+ confirm: bool = Form(default=False)):
+ pkgbase = get_pkg_or_base(name, PackageBase)
+
+ if not request.user.has_credential("CRED_PKGBASE_DELETE"):
+ return RedirectResponse(f"/pkgbase/{name}",
+ status_code=int(HTTPStatus.SEE_OTHER))
+
+ if not confirm:
+ context = make_context(request, "Package Deletion")
+ context["pkgbase"] = pkgbase
+ context["errors"] = [("The selected packages have not been deleted, "
+ "check the confirmation checkbox.")]
+ return render_template(request, "packages/delete.html", context,
+ status_code=int(HTTPStatus.EXPECTATION_FAILED))
+
+ packages = pkgbase.packages.all()
+ for package in packages:
+ delete_package(request.user, package)
+
+ return RedirectResponse("/packages", status_code=int(HTTPStatus.SEE_OTHER))
diff --git a/templates/packages/delete.html b/templates/packages/delete.html
new file mode 100644
index 00000000..6e882d05
--- /dev/null
+++ b/templates/packages/delete.html
@@ -0,0 +1,56 @@
+{% extends "partials/layout.html" %}
+
+{% block pageContent %}
+
+ {% if errors %}
+
+ {% for error in errors %}
+ - {{ error | tr }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
{{ "Delete Package" | tr }}: {{ pkgbase.Name }}
+
+
+ {{
+ "Use this form to delete the package base %s%s%s and "
+ "the following packages from the AUR: "
+ | tr | format("", pkgbase.Name, "") | safe
+ }}
+
+
+
+ {% for package in pkgbase.packages.all() %}
+ - {{ package.Name }}
+ {% endfor %}
+
+
+
+ {{
+ "Deletion of a package is permanent. "
+ "Select the checkbox to confirm action." | tr
+ }}
+
+
+
+
+
+{% endblock %}
diff --git a/test/test_packages_routes.py b/test/test_packages_routes.py
index c9622431..1f258497 100644
--- a/test/test_packages_routes.py
+++ b/test/test_packages_routes.py
@@ -1845,3 +1845,49 @@ def test_pkgbase_disown(client: TestClient, user: User, maintainer: User,
with client as request:
resp = request.post(endpoint, data={"confirm": True}, cookies=cookies)
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
+
+
+def test_pkgbase_delete_unauthorized(client: TestClient, user: User,
+ package: Package):
+ pkgbase = package.PackageBase
+ cookies = {"AURSID": user.login(Request(), "testPassword")}
+ endpoint = f"/pkgbase/{pkgbase.Name}/delete"
+
+ # Test GET.
+ with client as request:
+ resp = request.get(endpoint, cookies=cookies, allow_redirects=False)
+ assert resp.status_code == int(HTTPStatus.SEE_OTHER)
+ assert resp.headers.get("location") == f"/pkgbase/{pkgbase.Name}"
+
+ # Test POST.
+ with client as request:
+ resp = request.post(endpoint, cookies=cookies)
+ assert resp.status_code == int(HTTPStatus.SEE_OTHER)
+ assert resp.headers.get("location") == f"/pkgbase/{pkgbase.Name}"
+
+
+def test_pkgbase_delete(client: TestClient, tu_user: User, package: Package):
+ pkgbase = package.PackageBase
+
+ # Test that the GET request works.
+ cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
+ endpoint = f"/pkgbase/{pkgbase.Name}/delete"
+ with client as request:
+ resp = request.get(endpoint, cookies=cookies)
+ assert resp.status_code == int(HTTPStatus.OK)
+
+ # Test that POST works and denies us because we haven't confirmed.
+ with client as request:
+ resp = request.post(endpoint, cookies=cookies)
+ assert resp.status_code == int(HTTPStatus.EXPECTATION_FAILED)
+
+ # Test that we can actually delete the pkgbase.
+ with client as request:
+ resp = request.post(endpoint, data={"confirm": True}, cookies=cookies)
+ assert resp.status_code == int(HTTPStatus.SEE_OTHER)
+
+ # Let's assert that the package base record got removed.
+ record = db.query(PackageBase).filter(
+ PackageBase.Name == pkgbase.Name
+ ).first()
+ assert record is None