Skip to content

feat(admin): paginate/filter app listings, add deployment delete + purge#237

Merged
v0l merged 1 commit into
masterfrom
feat/admin-app-deployment-purge-and-pagination
Jul 26, 2026
Merged

feat(admin): paginate/filter app listings, add deployment delete + purge#237
v0l merged 1 commit into
masterfrom
feat/admin-app-deployment-purge-and-pagination

Conversation

@v0l

@v0l v0l commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #234
Fixes #235

Both issues land in one branch because they rewrite the same three DB query paths — doing them separately means rewriting list_all_app_deployments twice.

#235 — pagination, filters, include_deleted

GET /api/admin/v1/apps, /app_clusters and /app-deployments previously returned every row as a bare {"data": [...]} with no query parameters at all. They now accept limit/offset (default 50, max 100) and return the standard ApiPaginatedData envelope used by every other admin list, ordered id DESC.

Filters are pushed into SQL rather than applied in the handler, via a new FilteredQuery helper (lnvps_db/src/mysql.rs) that builds the COUNT(*) and the row page from one set of conditions:

Endpoint Filters
/app-deployments user_id, app_id, cluster_id, region_id, status, desired_state, search, include_deleted
/apps enabled, search
/app_clusters enabled, region_id, search

region_id on deployments resolves through the cluster. search is a case-insensitive substring match with LIKE wildcards escaped so the term matches literally.

Because deployment deletion is a soft delete, a deleted deployment was previously invisible to admins entirely. include_deleted=true (default false) now surfaces it, and AdminAppDeploymentInfo gains a deleted boolean.

Breaking for admin clients. The response shape of all three listings changes from {data} to {data, total, limit, offset}. The issue explicitly asks for the standard envelope so the dashboard's shared paginated table can drive these lists, so this takes the break rather than versioning the endpoints. Called out in API_CHANGELOG.md.

#234 — delete and purge

New DELETE /api/admin/v1/app-deployments/{id} (app_deployment::delete): deactivates billing and soft-deletes, and the operator tears down the namespace and volumes on its next reconcile. Following the rule VMs already use, a deployment whose first payment was never confirmed carries no billing history and is removed entirely instead.

Optional {"purge": true} hard-deletes the row together with its subscription, subscription_line_item and payment rows in one transaction. super_admin only, rejected 403 for everyone else, authorized before the lookup — exactly like the VM purge. An already soft-deleted deployment can still be purged; a plain delete of one returns 409.

The same purge flag is added to DELETE /api/admin/v1/subscriptions/{id}, bypassing the "N paid payments exist" guard and cascading line items and payments. Regular deletes keep today's behaviour.

Three decisions that differ from the issue text

1. The purge/teardown concern does not apply. #234 asks that a purge be "rejected (or deferred) until the operator has actually torn the namespace/PVCs down, so we don't orphan Kubernetes resources." It doesn't need to be. gc_namespaces (lnvps_operator/src/app_deployments.rs:1167) collects any app-{id} namespace whose id is absent from the set of live deployments — and a hard-deleted row is absent from that set exactly like a soft-deleted one. The namespace and its PVCs are torn down on the next reconcile either way. No deferral, no orphans.

2. No migration. #234 asks for the app_deployment::delete permission to be seeded. 20260725150000_app_deployment_rbac_permissions.sql already grants actions 0–3 on resource 27 to super_admin.

3. include_deleted only exists on deployments. #234 asks for it on all three listings, but app and app_cluster have no deleted column — only enabled, and the admin listings already returned disabled rows. A parameter that silently does nothing is worse than not having it, so those two get enabled and the docs say why.

Guards worth reviewing

  • hard_delete_app_deployment keeps the subscription if it still bills another resource, so a mixed subscription cannot take a VM's billing (and its FK) down with the deployment.
  • hard_delete_subscription refuses while a VM or app deployment still references one of the subscription's line items, and names which resource is in the way.

Adjacent pre-existing bug, deliberately not fixed here

admin_delete_app and admin_delete_app_cluster guard on list_all_app_deployments(), which excludes soft-deleted rows — but a soft-deleted deployment still holds the FK to app / app_cluster. Deleting an app whose only deployments are soft-deleted passes the guard and then fails on the foreign key as a 500. Customers could already reach that state via the customer delete before this PR, so it is not new; left alone rather than widening scope. Happy to file it separately.

Testing

734 unit tests pass (cargo test --workspace --exclude lnvps_e2e -- --test-threads=1) at fce5788, working tree clean. cargo clippy is clean on every file touched.

New coverage:

  • 5 mock tests (lnvps_api_common/src/mock.rs) — every filter on all three listings, pagination totals versus page size, blank-search handling, include_deleted, both hard-delete cascades, the mixed-subscription carve-out, and both purge guards.
  • 5 e2e tests (lnvps_e2e/src/admin_api.rs, rbac.rs) — the paginated envelope, filters and include_deleted over HTTP, the soft-delete → 409 → purge sequence with DB assertions, the never-paid purge, the subscription purge including the refusal paths, and 403 for a role that holds app_deployment::delete but is not super_admin.

⚠️ The e2e tests compile but have not been run. Docker was not available in the environment this was developed in, so they have never executed against MariaDB. They need a CI run (or docker compose up -d locally) before this merges.

Two build failures in that environment are pre-existing and reproduce on master: lnvps_health does not build on macOS (Linux-only libc MTU constants) and lnvps_api --all-features has a BitvoraNode trait error.


Opened from the general Buzz channel (f5894ea9-44c7-56fb-bd9b-6e3e671e4bc1).

The three admin app listings returned every row as a bare `{data: [...]}`
with no query parameters at all, and there was no way to delete an app
deployment or to remove a subscription that had ever been paid. Both
issues land together because they rewrite the same DB query paths.

Pagination and filters (#235)

`GET /api/admin/v1/apps`, `/app_clusters` and `/app-deployments` now take
limit/offset (default 50, max 100) and return the standard
ApiPaginatedData envelope every other admin list uses, ordered id DESC.
Filters are pushed into SQL via a new FilteredQuery helper that builds
the COUNT and the page from one set of conditions, rather than being
applied in the handler.

Deployment deletion is a soft delete, so a deleted deployment was
previously invisible to admins entirely. `include_deleted=true` now
surfaces it and AdminAppDeploymentInfo carries `deleted`. `app` and
`app_cluster` have no soft-delete, so the flag does not apply there -
`enabled` is their only visibility filter.

Delete and purge (#234)

New `DELETE /api/admin/v1/app-deployments/{id}`: deactivates billing and
soft-deletes, and - following the VM rule - removes a never-paid
deployment entirely. `{"purge": true}` hard-deletes the row with its
subscription, line items and payments in one transaction, super_admin
only, authorized before the lookup exactly like the VM purge. The
subscription is kept if it still bills another resource, so a mixed
subscription cannot take a VM's billing down with it.

Purging does not orphan Kubernetes resources: the operator GCs any
`app-{id}` namespace absent from its active set, and a purged row is
absent exactly like a soft-deleted one.

The same `purge` flag on `DELETE /api/admin/v1/subscriptions/{id}`
bypasses the paid-payments guard, refusing while a VM or deployment
still references one of the line items.

No migration needed - the app_deployment permission set including
`delete` was already seeded for super_admin.

Fixes #234
Fixes #235

Co-authored-by: Kieran <kieran@harkin.me>
Signed-off-by: Kieran <kieran@harkin.me>
@v0l v0l added enhancement New feature or request api User-facing or admin API changes labels Jul 26, 2026
@v0l
v0l merged commit cb05c05 into master Jul 26, 2026
6 checks passed
@v0l
v0l deleted the feat/admin-app-deployment-purge-and-pagination branch July 26, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api User-facing or admin API changes enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admin app listings: no filters, no include_deleted, no pagination Admin: hard delete (purge) app deployments and subscriptions

1 participant