diff --git a/apps/gateway/src/main.rs b/apps/gateway/src/main.rs index 04dea66..5b970a9 100644 --- a/apps/gateway/src/main.rs +++ b/apps/gateway/src/main.rs @@ -79,6 +79,7 @@ impl Services { || path.starts_with("/api/notifications") || path.starts_with("/api/config") || path.starts_with("/api/kb") + || path.starts_with("/api/packages") || path.starts_with("/api/support") || path.starts_with("/api/admin/kb") || path.starts_with("/api/admin/support-cases") diff --git a/apps/users/src/handlers/pricing.rs b/apps/users/src/handlers/pricing.rs index c469fdf..56647e0 100644 --- a/apps/users/src/handlers/pricing.rs +++ b/apps/users/src/handlers/pricing.rs @@ -12,6 +12,12 @@ use uuid::Uuid; // ── Routers ─────────────────────────────────────────────────────────────────── +/// Public (user-facing) — list active packages for their role +pub fn public_packages_router() -> Router { + Router::new().route("/", get(public_list_packages)) +} + +/// Admin CRUD pub fn packages_router() -> Router { Router::new() .route("/", get(list_packages).post(create_package)) @@ -69,6 +75,64 @@ struct DateRangeQuery { // ── Package handlers ────────────────────────────────────────────────────────── +#[derive(Deserialize)] +struct PackageQuery { + role: Option, +} + +async fn public_list_packages( + State(state): State, + Query(params): Query, +) -> impl IntoResponse { + let rows = if let Some(role) = params.role { + sqlx::query!( + r#" + SELECT id, name, role_key, package_type, tracecoins_amount, price_inr, description, is_active + FROM pricing_packages + WHERE is_active = true AND (role_key = $1 OR role_key = 'ALL') + ORDER BY price_inr + "#, + role + ) + .fetch_all(&state.pool) + .await + } else { + sqlx::query!( + r#" + SELECT id, name, role_key, package_type, tracecoins_amount, price_inr, description, is_active + FROM pricing_packages + WHERE is_active = true + ORDER BY role_key, price_inr + "# + ) + .fetch_all(&state.pool) + .await + }; + + match rows { + Ok(rows) => { + let dtos: Vec = rows + .into_iter() + .map(|r| PackageDto { + id: r.id, + name: r.name, + role_key: r.role_key, + package_type: r.package_type, + tracecoins_amount: r.tracecoins_amount, + price_inr: r.price_inr, + description: r.description, + is_active: r.is_active, + }) + .collect(); + (StatusCode::OK, Json(serde_json::json!({ "packages": dtos }))).into_response() + } + Err(e) => { + tracing::error!("Failed to list packages: {e}"); + (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to load packages" }))).into_response() + } + } +} + async fn list_packages( _auth: AuthUser, State(state): State, diff --git a/apps/users/src/main.rs b/apps/users/src/main.rs index daf70ce..678aab3 100644 --- a/apps/users/src/main.rs +++ b/apps/users/src/main.rs @@ -90,6 +90,8 @@ async fn main() { // ── Coupons & Discounts (admin) ─────────────────────────────────── .nest("/api/admin/coupons", handlers::coupons::coupons_router()) .nest("/api/admin/discounts", handlers::coupons::discounts_router()) + // ── Tracecoin Packages (public) ─────────────────────────────────── + .nest("/api/packages", handlers::pricing::public_packages_router()) // ── Tracecoin Packages & Reports (admin) ────────────────────────── .nest("/api/admin/tracecoin-packages", handlers::pricing::packages_router()) .nest("/api/admin/reports", handlers::pricing::reports_router())