From a38291d1497f569b756ea01483c4d01bc1e66daf Mon Sep 17 00:00:00 2001 From: Kieran Date: Sun, 22 Feb 2026 21:53:26 +0000 Subject: [PATCH] feat(admin-api): add fields to sales report Include user_id, host_id, host_name, region_id, region_name in each payment record response for the time-series report endpoint. This enables client-side filtering by user, host, or region. Changes: - Add user_id, host_id, host_name, region_id, region_name to response - Update VmPaymentWithCompany model with new fields - Update SQL query to select additional fields - Update mock implementation - Update API documentation and changelog Closes #64 --- ADMIN_API_ENDPOINTS.md | 7 +++- API_CHANGELOG.md | 4 ++ lnvps_api_admin/src/admin/reports.rs | 13 ++++++ lnvps_api_common/src/mock.rs | 5 +++ lnvps_db/src/model.rs | 8 ++++ lnvps_db/src/mysql.rs | 63 +++++++++++++--------------- 6 files changed, 65 insertions(+), 35 deletions(-) diff --git a/ADMIN_API_ENDPOINTS.md b/ADMIN_API_ENDPOINTS.md index 0cba8211..e743803e 100644 --- a/ADMIN_API_ENDPOINTS.md +++ b/ADMIN_API_ENDPOINTS.md @@ -2494,7 +2494,12 @@ Response: "tax": 26250, "company_id": 1, "company_name": "Acme Corp", - "company_base_currency": "USD" + "company_base_currency": "USD", + "user_id": 456, + "host_id": 2, + "host_name": "host-01", + "region_id": 1, + "region_name": "US East" } ] } diff --git a/API_CHANGELOG.md b/API_CHANGELOG.md index ee0010b6..f3b823a1 100644 --- a/API_CHANGELOG.md +++ b/API_CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added +- **2026-02-22** - Added additional fields to sales time-series report + - `GET /api/admin/v1/reports/time-series` — Response now includes `user_id`, `host_id`, `host_name`, `region_id`, `region_name` fields in each payment record + - Enables client-side filtering by user, host, or region + - **2026-02-21** - Added endpoint to list free IPs in an IPv4 range (Admin API) - `GET /api/admin/v1/ip_ranges/{id}/free_ips` — Returns list of unassigned IP addresses - Only available for IPv4 ranges; IPv6 ranges return an error (too large to enumerate) diff --git a/lnvps_api_admin/src/admin/reports.rs b/lnvps_api_admin/src/admin/reports.rs index 887180af..b0f647cc 100644 --- a/lnvps_api_admin/src/admin/reports.rs +++ b/lnvps_api_admin/src/admin/reports.rs @@ -76,6 +76,14 @@ struct TimeSeriesPayment { company_id: u64, company_name: String, company_base_currency: String, + // User information + user_id: u64, + // Host information + host_id: u64, + host_name: String, + // Region information + region_id: u64, + region_name: String, } #[derive(Serialize, Deserialize)] @@ -156,6 +164,11 @@ async fn admin_time_series_report( company_id: payment.company_id, company_name: payment.company_name.clone(), company_base_currency: payment.company_base_currency.clone(), + user_id: payment.user_id, + host_id: payment.host_id, + host_name: payment.host_name.clone(), + region_id: payment.region_id, + region_name: payment.region_name.clone(), }); } diff --git a/lnvps_api_common/src/mock.rs b/lnvps_api_common/src/mock.rs index c5d6d3ad..44185b8d 100644 --- a/lnvps_api_common/src/mock.rs +++ b/lnvps_api_common/src/mock.rs @@ -2093,6 +2093,11 @@ impl lnvps_db::AdminDb for MockDb { company_id: region_company_id, company_name: company.name.clone(), company_base_currency: company.base_currency.clone(), + user_id: vm.user_id, + host_id: host.id, + host_name: host.name.clone(), + region_id: region.id, + region_name: region.name.clone(), }); } } diff --git a/lnvps_db/src/model.rs b/lnvps_db/src/model.rs index 625a859b..288bdedd 100644 --- a/lnvps_db/src/model.rs +++ b/lnvps_db/src/model.rs @@ -982,6 +982,14 @@ pub struct VmPaymentWithCompany { pub company_id: u64, pub company_name: String, pub company_base_currency: String, + // User information + pub user_id: u64, + // Host information + pub host_id: u64, + pub host_name: String, + // Region information + pub region_id: u64, + pub region_name: String, } #[derive(Type, Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] diff --git a/lnvps_db/src/mysql.rs b/lnvps_db/src/mysql.rs index b27c9ca0..e58487b4 100644 --- a/lnvps_db/src/mysql.rs +++ b/lnvps_db/src/mysql.rs @@ -3165,41 +3165,36 @@ impl AdminDb for LNVpsDbMysql { company_id: u64, currency: Option<&str>, ) -> DbResult> { - match currency { - Some(currency) => { - Ok(sqlx::query_as( - "SELECT vp.*, c.id as company_id, c.name as company_name, c.base_currency as company_base_currency - FROM vm_payment vp - JOIN vm v ON vp.vm_id = v.id - JOIN vm_host vh ON v.host_id = vh.id - JOIN vm_host_region vhr ON vh.region_id = vhr.id - JOIN company c ON vhr.company_id = c.id - WHERE vp.created >= ? AND vp.created < ? AND vp.is_paid = true AND c.id = ? AND vp.currency = ? - ORDER BY vp.created" - ) - .bind(start_date) - .bind(end_date) - .bind(company_id) - .bind(currency) - .fetch_all(&self.db).await?) - }, - None => { - Ok(sqlx::query_as( - "SELECT vp.*, c.id as company_id, c.name as company_name, c.base_currency as company_base_currency - FROM vm_payment vp - JOIN vm v ON vp.vm_id = v.id - JOIN vm_host vh ON v.host_id = vh.id - JOIN vm_host_region vhr ON vh.region_id = vhr.id - JOIN company c ON vhr.company_id = c.id - WHERE vp.created >= ? AND vp.created < ? AND vp.is_paid = true AND c.id = ? - ORDER BY vp.created" - ) - .bind(start_date) - .bind(end_date) - .bind(company_id) - .fetch_all(&self.db).await?) - } + let mut query = QueryBuilder::new( + "SELECT vp.*, + c.id as company_id, c.name as company_name, c.base_currency as company_base_currency, + v.user_id, + vh.id as host_id, vh.name as host_name, + vhr.id as region_id, vhr.name as region_name + FROM vm_payment vp + JOIN vm v ON vp.vm_id = v.id + JOIN vm_host vh ON v.host_id = vh.id + JOIN vm_host_region vhr ON vh.region_id = vhr.id + JOIN company c ON vhr.company_id = c.id + WHERE vp.created >= ", + ); + query.push_bind(start_date); + query.push(" AND vp.created < "); + query.push_bind(end_date); + query.push(" AND vp.is_paid = true AND c.id = "); + query.push_bind(company_id); + + if let Some(currency) = currency { + query.push(" AND vp.currency = "); + query.push_bind(currency); } + + query.push(" ORDER BY vp.created"); + + Ok(query + .build_query_as::() + .fetch_all(&self.db) + .await?) } async fn admin_get_referral_usage_by_date_range(