omni_orchestrator/schemas/v1/api/
cost.rs

1//! Cost management module for handling cost tracking and analysis operations.
2//!
3//! This module provides a REST API for managing cost-related entities, including:
4//! - Resource types management
5//! - Cost metrics tracking and analysis
6//! - Cost projections and forecasting
7//! - Budget management
8//! - Resource pricing management
9//! - Cost allocation tagging
10
11use std::sync::Arc;
12use super::super::super::auth::User;
13use crate::DatabaseManager;
14use crate::models::{
15    util_tables::ResourceType,
16    cost::{
17        CostBudget,
18        CostMetric,
19        CostProjection,
20        ResourcePricing,
21        CostAllocationTag,
22        CostMetricWithType,
23    }
24};
25use super::super::db::queries as db;
26use rocket::http::Status;
27use rocket::serde::json::{json, Json, Value};
28use rocket::{delete, get, post, put, State};
29use serde::{Deserialize, Serialize};
30use chrono::{DateTime, Utc};
31
32/// Request data for creating a new resource type.
33#[derive(Debug, Serialize, Deserialize)]
34pub struct CreateResourceTypeRequest {
35    /// Name of the resource type
36    name: String,
37    /// Category of the resource
38    category: String,
39    /// Unit of measurement (e.g., 'vCPU-hour', 'GB-month')
40    unit_of_measurement: String,
41    /// Optional description of the resource type
42    description: Option<String>,
43}
44
45/// Request data for updating a resource type.
46#[derive(Debug, Serialize, Deserialize)]
47pub struct UpdateResourceTypeRequest {
48    /// New name for the resource type
49    name: Option<String>,
50    /// New category for the resource
51    category: Option<String>,
52    /// New unit of measurement
53    unit_of_measurement: Option<String>,
54    /// New description of the resource type
55    description: Option<String>,
56}
57
58/// Request data for creating a new cost metric.
59#[derive(Debug, Serialize, Deserialize)]
60pub struct CreateCostMetricRequest {
61    /// Resource type ID
62    resource_type_id: i32,
63    /// Provider ID
64    provider_id: Option<i64>,
65    /// Region ID
66    region_id: Option<i64>,
67    /// Application ID
68    app_id: Option<i64>,
69    /// Worker ID
70    worker_id: Option<i64>,
71    /// Organization ID
72    org_id: Option<i64>,
73    /// Start time of the usage period
74    start_time: DateTime<Utc>,
75    /// End time of the usage period
76    end_time: DateTime<Utc>,
77    /// Amount of resource used
78    usage_quantity: f64,
79    /// Cost per unit
80    unit_cost: f64,
81    /// Currency code (e.g., 'USD')
82    currency: String,
83    /// Total cost for this usage
84    total_cost: f64,
85    /// Discount percentage applied
86    discount_percentage: Option<f64>,
87    /// Reason for the discount
88    discount_reason: Option<String>,
89    /// Billing period (e.g., '2025-05')
90    billing_period: Option<String>,
91}
92
93/// Request data for filtering cost metrics.
94#[derive(Debug, Serialize, Deserialize)]
95pub struct CostMetricFilter {
96    /// Filter by resource type ID
97    resource_type_id: Option<i32>,
98    /// Filter by provider ID
99    provider_id: Option<i64>,
100    /// Filter by application ID
101    app_id: Option<i64>,
102    /// Filter by start date
103    start_date: Option<DateTime<Utc>>,
104    /// Filter by end date
105    end_date: Option<DateTime<Utc>>,
106    /// Filter by billing period
107    billing_period: Option<String>,
108}
109
110/// Request data for creating a new cost budget.
111#[derive(Debug, Serialize, Deserialize)]
112pub struct CreateCostBudgetRequest {
113    /// Organization ID
114    org_id: i64,
115    /// Application ID (optional)
116    app_id: Option<i64>,
117    /// Budget name
118    budget_name: String,
119    /// Budget amount
120    budget_amount: f64,
121    /// Currency code (e.g., 'USD')
122    currency: String,
123    /// Budget period type
124    budget_period: String,
125    /// Start date of the budget period
126    period_start: DateTime<Utc>,
127    /// End date of the budget period
128    period_end: DateTime<Utc>,
129    /// Alert threshold percentage
130    alert_threshold_percentage: f64,
131    /// Contacts to alert when threshold is reached (JSON)
132    alert_contacts: String,
133}
134
135/// Request data for updating a cost budget.
136#[derive(Debug, Serialize, Deserialize)]
137pub struct UpdateCostBudgetRequest {
138    /// New budget name
139    budget_name: Option<String>,
140    /// New budget amount
141    budget_amount: Option<f64>,
142    /// New alert threshold percentage
143    alert_threshold_percentage: Option<f64>,
144    /// New contacts to alert when threshold is reached (JSON)
145    alert_contacts: Option<String>,
146    /// Whether the budget is active
147    is_active: Option<bool>,
148}
149
150/// Request data for creating a new cost projection.
151#[derive(Debug, Serialize, Deserialize)]
152pub struct CreateCostProjectionRequest {
153    /// Organization ID
154    org_id: i64,
155    /// Application ID (optional)
156    app_id: Option<i64>,
157    /// Projection period type (e.g., 'monthly', 'quarterly')
158    projection_period: String,
159    /// Start date of the projection period
160    start_date: DateTime<Utc>,
161    /// End date of the projection period
162    end_date: DateTime<Utc>,
163    /// Projected cost amount
164    projected_cost: f64,
165    /// Currency code (e.g., 'USD')
166    currency: String,
167    /// Projection model used (e.g., 'linear', 'average_30d')
168    projection_model: String,
169    /// Confidence level of the projection
170    confidence_level: Option<f64>,
171    /// Additional metadata about the projection (JSON)
172    metadata: Option<String>,
173}
174
175/// Request data for creating a new resource pricing entry.
176#[derive(Debug, Serialize, Deserialize)]
177pub struct CreateResourcePricingRequest {
178    /// Resource type ID
179    resource_type_id: i32,
180    /// Provider ID
181    provider_id: i64,
182    /// Region ID (optional)
183    region_id: Option<i64>,
184    /// Tier name (e.g., 'standard', 'premium')
185    tier_name: String,
186    /// Price per unit
187    unit_price: f64,
188    /// Currency code (e.g., 'USD')
189    currency: String,
190    /// When this pricing becomes effective
191    effective_from: DateTime<Utc>,
192    /// When this pricing expires (optional)
193    effective_to: Option<DateTime<Utc>>,
194    /// Pricing model (e.g., 'on-demand', 'reserved')
195    pricing_model: String,
196    /// Commitment period (e.g., '1-year', '3-year')
197    commitment_period: Option<String>,
198    /// Volume discount tiers (JSON)
199    volume_discount_tiers: Option<String>,
200}
201
202/// Request data for updating a resource pricing entry.
203#[derive(Debug, Serialize, Deserialize)]
204pub struct UpdateResourcePricingRequest {
205    /// New price per unit
206    unit_price: Option<f64>,
207    /// New expiration date
208    effective_to: Option<DateTime<Utc>>,
209    /// New volume discount tiers (JSON)
210    volume_discount_tiers: Option<String>,
211}
212
213/// Request data for creating a new cost allocation tag.
214#[derive(Debug, Serialize, Deserialize)]
215pub struct CreateCostAllocationTagRequest {
216    /// Tag key
217    tag_key: String,
218    /// Tag value
219    tag_value: String,
220    /// Resource ID
221    resource_id: i64,
222    /// Resource type
223    resource_type: String,
224}
225
226/// Request data for aggregate cost analysis by dimension.
227#[derive(Debug, Serialize, Deserialize)]
228pub struct CostAnalysisByDimensionRequest {
229    /// Dimension to group by
230    dimension: String,
231    /// Start date for analysis
232    start_date: DateTime<Utc>,
233    /// End date for analysis
234    end_date: DateTime<Utc>,
235    /// Maximum number of results to return
236    limit: i64,
237}
238
239/// Request data for cost analysis over time.
240#[derive(Debug, Serialize, Deserialize)]
241pub struct CostOverTimeRequest {
242    /// Application ID to analyze
243    app_id: i64,
244    /// Time interval ('day', 'week', 'month')
245    interval: String,
246    /// Start date for analysis
247    start_date: DateTime<Utc>,
248    /// End date for analysis
249    end_date: DateTime<Utc>,
250}
251
252/// List all resource types with pagination support.
253#[get("/platform/<platform_id>/resource_types?<page>&<per_page>")]
254pub async fn list_resource_types(
255    platform_id: i64,
256    page: Option<i64>,
257    per_page: Option<i64>,
258    db_manager: &State<Arc<DatabaseManager>>,
259) -> Result<Json<Value>, (Status, Json<Value>)> {
260    // Get platform information
261    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
262        Ok(platform) => platform,
263        Err(_) => {
264            return Err((
265                Status::NotFound,
266                Json(json!({
267                    "error": "Platform not found",
268                    "message": format!("Platform with ID {} does not exist", platform_id)
269                }))
270            ));
271        }
272    };
273
274    // Get platform-specific database pool
275    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
276        Ok(pool) => pool,
277        Err(_) => {
278            return Err((
279                Status::InternalServerError,
280                Json(json!({
281                    "error": "Database error",
282                    "message": "Failed to connect to platform database"
283                }))
284            ));
285        }
286    };
287
288    match (page, per_page) {
289        (Some(p), Some(pp)) => {
290            let resource_types = match db::cost::list_resource_types(&pool, p, pp).await {
291                Ok(types) => types,
292                Err(_) => {
293                    return Err((
294                        Status::InternalServerError,
295                        Json(json!({
296                            "error": "Database error",
297                            "message": "Failed to retrieve resource types"
298                        }))
299                    ));
300                }
301            };
302            
303            let total_count = match db::cost::count_resource_types(&pool).await {
304                Ok(count) => count,
305                Err(_) => {
306                    return Err((
307                        Status::InternalServerError,
308                        Json(json!({
309                            "error": "Database error",
310                            "message": "Failed to count resource types"
311                        }))
312                    ));
313                }
314            };
315            
316            let total_pages = (total_count as f64 / pp as f64).ceil() as i64;
317
318            let response = json!({
319                "resource_types": resource_types,
320                "pagination": {
321                    "page": p,
322                    "per_page": pp,
323                    "total_count": total_count,
324                    "total_pages": total_pages
325                }
326            });
327
328            Ok(Json(response))
329        }
330        _ => Err((
331            Status::BadRequest,
332            Json(json!({
333                "error": "Missing pagination parameters",
334                "message": "Please provide both 'page' and 'per_page' parameters"
335            }))
336        ))
337    }
338}
339
340/// Count the total number of resource types.
341#[get("/platform/<platform_id>/count/resource_types")]
342pub async fn count_resource_types(
343    platform_id: i64,
344    db_manager: &State<Arc<DatabaseManager>>,
345) -> Result<Json<i64>, (Status, Json<Value>)> {
346    // Get platform information
347    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
348        Ok(platform) => platform,
349        Err(_) => {
350            return Err((
351                Status::NotFound,
352                Json(json!({
353                    "error": "Platform not found",
354                    "message": format!("Platform with ID {} does not exist", platform_id)
355                }))
356            ));
357        }
358    };
359
360    // Get platform-specific database pool
361    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
362        Ok(pool) => pool,
363        Err(_) => {
364            return Err((
365                Status::InternalServerError,
366                Json(json!({
367                    "error": "Database error",
368                    "message": "Failed to connect to platform database"
369                }))
370            ));
371        }
372    };
373
374    match db::cost::count_resource_types(&pool).await {
375        Ok(count) => Ok(Json(count)),
376        Err(_) => Err((
377            Status::InternalServerError,
378            Json(json!({
379                "error": "Database error",
380                "message": "Failed to count resource types"
381            }))
382        )),
383    }
384}
385
386/// Get a specific resource type by ID.
387#[get("/platform/<platform_id>/resource_types/<id>")]
388pub async fn get_resource_type(
389    platform_id: i64,
390    id: i32,
391    db_manager: &State<Arc<DatabaseManager>>,
392) -> Result<Json<ResourceType>, (Status, Json<Value>)> {
393    // Get platform information
394    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
395        Ok(platform) => platform,
396        Err(_) => {
397            return Err((
398                Status::NotFound,
399                Json(json!({
400                    "error": "Platform not found",
401                    "message": format!("Platform with ID {} does not exist", platform_id)
402                }))
403            ));
404        }
405    };
406
407    // Get platform-specific database pool
408    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
409        Ok(pool) => pool,
410        Err(_) => {
411            return Err((
412                Status::InternalServerError,
413                Json(json!({
414                    "error": "Database error",
415                    "message": "Failed to connect to platform database"
416                }))
417            ));
418        }
419    };
420
421    match db::cost::get_resource_type_by_id(&pool, id).await {
422        Ok(resource_type) => Ok(Json(resource_type)),
423        Err(_) => Err((
424            Status::NotFound,
425            Json(json!({
426                "error": "Resource type not found",
427                "message": format!("Resource type with ID {} could not be found", id)
428            }))
429        )),
430    }
431}
432
433/// Create a new resource type.
434#[post("/platform/<platform_id>/resource_types", format = "json", data = "<request>")]
435pub async fn create_resource_type(
436    platform_id: i64,
437    request: Json<CreateResourceTypeRequest>,
438    db_manager: &State<Arc<DatabaseManager>>,
439) -> Result<Json<ResourceType>, (Status, Json<Value>)> {
440    // Get platform information
441    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
442        Ok(platform) => platform,
443        Err(_) => {
444            return Err((
445                Status::NotFound,
446                Json(json!({
447                    "error": "Platform not found",
448                    "message": format!("Platform with ID {} does not exist", platform_id)
449                }))
450            ));
451        }
452    };
453
454    // Get platform-specific database pool
455    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
456        Ok(pool) => pool,
457        Err(_) => {
458            return Err((
459                Status::InternalServerError,
460                Json(json!({
461                    "error": "Database error",
462                    "message": "Failed to connect to platform database"
463                }))
464            ));
465        }
466    };
467
468    match db::cost::create_resource_type(
469        &pool,
470        &request.name,
471        &request.category,
472        &request.unit_of_measurement,
473        request.description.as_deref(),
474    ).await {
475        Ok(resource_type) => Ok(Json(resource_type)),
476        Err(e) => Err((
477            Status::InternalServerError,
478            Json(json!({
479                "error": "Failed to create resource type",
480                "message": format!("{}", e)
481            }))
482        )),
483    }
484}
485
486/// Update an existing resource type.
487#[put("/platform/<platform_id>/resource_types/<id>", format = "json", data = "<request>")]
488pub async fn update_resource_type(
489    platform_id: i64,
490    id: i32,
491    request: Json<UpdateResourceTypeRequest>,
492    db_manager: &State<Arc<DatabaseManager>>,
493) -> Result<Json<ResourceType>, (Status, Json<Value>)> {
494    // Get platform information
495    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
496        Ok(platform) => platform,
497        Err(_) => {
498            return Err((
499                Status::NotFound,
500                Json(json!({
501                    "error": "Platform not found",
502                    "message": format!("Platform with ID {} does not exist", platform_id)
503                }))
504            ));
505        }
506    };
507
508    // Get platform-specific database pool
509    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
510        Ok(pool) => pool,
511        Err(_) => {
512            return Err((
513                Status::InternalServerError,
514                Json(json!({
515                    "error": "Database error",
516                    "message": "Failed to connect to platform database"
517                }))
518            ));
519        }
520    };
521
522    match db::cost::update_resource_type(
523        &pool,
524        id,
525        request.name.as_deref(),
526        request.category.as_deref(),
527        request.unit_of_measurement.as_deref(),
528        request.description.as_deref(),
529    ).await {
530        Ok(resource_type) => Ok(Json(resource_type)),
531        Err(e) => Err((
532            Status::InternalServerError,
533            Json(json!({
534                "error": "Failed to update resource type",
535                "message": format!("{}", e)
536            }))
537        )),
538    }
539}
540
541/// Delete a resource type.
542#[delete("/platform/<platform_id>/resource_types/<id>")]
543pub async fn delete_resource_type(
544    platform_id: i64,
545    id: i32,
546    db_manager: &State<Arc<DatabaseManager>>,
547) -> Result<Json<Value>, (Status, Json<Value>)> {
548    // Get platform information
549    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
550        Ok(platform) => platform,
551        Err(_) => {
552            return Err((
553                Status::NotFound,
554                Json(json!({
555                    "error": "Platform not found",
556                    "message": format!("Platform with ID {} does not exist", platform_id)
557                }))
558            ));
559        }
560    };
561
562    // Get platform-specific database pool
563    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
564        Ok(pool) => pool,
565        Err(_) => {
566            return Err((
567                Status::InternalServerError,
568                Json(json!({
569                    "error": "Database error",
570                    "message": "Failed to connect to platform database"
571                }))
572            ));
573        }
574    };
575
576    match db::cost::delete_resource_type(&pool, id).await {
577        Ok(_) => Ok(Json(json!({ "status": "deleted" }))),
578        Err(e) => Err((
579            Status::InternalServerError,
580            Json(json!({
581                "error": "Failed to delete resource type",
582                "message": format!("{}", e)
583            }))
584        )),
585    }
586}
587
588// Cost Metrics Routes
589
590/// List cost metrics with pagination and filtering support.
591#[get("/platform/<platform_id>/cost_metrics?<page>&<per_page>&<resource_type_id>&<provider_id>&<app_id>&<start_date>&<end_date>&<billing_period>")]
592pub async fn list_cost_metrics(
593    platform_id: i64,
594    page: Option<i64>,
595    per_page: Option<i64>,
596    resource_type_id: Option<i32>,
597    provider_id: Option<i64>,
598    app_id: Option<i64>,
599    start_date: Option<String>,
600    end_date: Option<String>,
601    billing_period: Option<String>,
602    db_manager: &State<Arc<DatabaseManager>>,
603) -> Result<Json<Value>, (Status, Json<Value>)> {
604    // Get platform information
605    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
606        Ok(platform) => platform,
607        Err(_) => {
608            return Err((
609                Status::NotFound,
610                Json(json!({
611                    "error": "Platform not found",
612                    "message": format!("Platform with ID {} does not exist", platform_id)
613                }))
614            ));
615        }
616    };
617
618    // Get platform-specific database pool
619    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
620        Ok(pool) => pool,
621        Err(_) => {
622            return Err((
623                Status::InternalServerError,
624                Json(json!({
625                    "error": "Database error",
626                    "message": "Failed to connect to platform database"
627                }))
628            ));
629        }
630    };
631
632    use chrono::TimeZone;
633
634    // Parse start_date and end_date from Option<String> to Option<DateTime<Utc>>
635    let parsed_start_date = match start_date {
636        Some(ref s) => match DateTime::parse_from_rfc3339(s) {
637            Ok(dt) => Some(dt.with_timezone(&Utc)),
638            Err(_) => None,
639        },
640        None => None,
641    };
642    let parsed_end_date = match end_date {
643        Some(ref s) => match DateTime::parse_from_rfc3339(s) {
644            Ok(dt) => Some(dt.with_timezone(&Utc)),
645            Err(_) => None,
646        },
647        None => None,
648    };
649
650    match (page, per_page) {
651        (Some(p), Some(pp)) => {
652            let cost_metrics = match db::cost::list_cost_metrics(
653                &pool, p, pp, resource_type_id, provider_id, app_id, parsed_start_date, parsed_end_date, billing_period.as_deref()
654            ).await {
655                Ok(metrics) => metrics,
656                Err(_) => {
657                    return Err((
658                        Status::InternalServerError,
659                        Json(json!({
660                            "error": "Database error",
661                            "message": "Failed to retrieve cost metrics"
662                        }))
663                    ));
664                }
665            };
666            
667            let total_count = match db::cost::count_cost_metrics(
668                &pool, resource_type_id, provider_id, app_id, parsed_start_date, parsed_end_date, billing_period.as_deref()
669            ).await {
670                Ok(count) => count,
671                Err(_) => {
672                    return Err((
673                        Status::InternalServerError,
674                        Json(json!({
675                            "error": "Database error",
676                            "message": "Failed to count cost metrics"
677                        }))
678                    ));
679                }
680            };
681            
682            let total_pages = (total_count as f64 / pp as f64).ceil() as i64;
683
684            let response = json!({
685                "cost_metrics": cost_metrics,
686                "pagination": {
687                    "page": p,
688                    "per_page": pp,
689                    "total_count": total_count,
690                    "total_pages": total_pages
691                }
692            });
693
694            Ok(Json(response))
695        }
696        _ => Err((
697            Status::BadRequest,
698            Json(json!({
699                "error": "Missing pagination parameters",
700                "message": "Please provide both 'page' and 'per_page' parameters"
701            }))
702        ))
703    }
704}
705
706/// Get a specific cost metric by ID.
707#[get("/platform/<platform_id>/cost_metrics/<id>")]
708pub async fn get_cost_metric(
709    platform_id: i64,
710    id: i64,
711    db_manager: &State<Arc<DatabaseManager>>,
712) -> Result<Json<CostMetricWithType>, (Status, Json<Value>)> {
713    // Get platform information
714    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
715        Ok(platform) => platform,
716        Err(_) => {
717            return Err((
718                Status::NotFound,
719                Json(json!({
720                    "error": "Platform not found",
721                    "message": format!("Platform with ID {} does not exist", platform_id)
722                }))
723            ));
724        }
725    };
726
727    // Get platform-specific database pool
728    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
729        Ok(pool) => pool,
730        Err(_) => {
731            return Err((
732                Status::InternalServerError,
733                Json(json!({
734                    "error": "Database error",
735                    "message": "Failed to connect to platform database"
736                }))
737            ));
738        }
739    };
740
741    match db::cost::get_cost_metric_by_id(&pool, id).await {
742        Ok(cost_metric) => Ok(Json(cost_metric)),
743        Err(e) => Err((
744            Status::NotFound,
745            Json(json!({
746                "error": "Cost metric not found",
747                "message": format!("Cost metric with ID {} could not be found: {}", id, e)
748            }))
749        )),
750    }
751}
752
753/// Create a new cost metric.
754#[post("/platform/<platform_id>/cost_metrics", format = "json", data = "<request>")]
755pub async fn create_cost_metric(
756    platform_id: i64,
757    request: Json<CreateCostMetricRequest>,
758    db_manager: &State<Arc<DatabaseManager>>,
759) -> Result<Json<CostMetric>, (Status, Json<Value>)> {
760    // Get platform information
761    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
762        Ok(platform) => platform,
763        Err(_) => {
764            return Err((
765                Status::NotFound,
766                Json(json!({
767                    "error": "Platform not found",
768                    "message": format!("Platform with ID {} does not exist", platform_id)
769                }))
770            ));
771        }
772    };
773
774    // Get platform-specific database pool
775    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
776        Ok(pool) => pool,
777        Err(_) => {
778            return Err((
779                Status::InternalServerError,
780                Json(json!({
781                    "error": "Database error",
782                    "message": "Failed to connect to platform database"
783                }))
784            ));
785        }
786    };
787
788    match db::cost::create_cost_metric(
789        &pool,
790        request.resource_type_id,
791        request.provider_id,
792        request.region_id,
793        request.app_id,
794        request.worker_id,
795        request.org_id,
796        request.start_time,
797        request.end_time,
798        request.usage_quantity,
799        request.unit_cost,
800        &request.currency,
801        request.total_cost,
802        request.discount_percentage,
803        request.discount_reason.as_deref(),
804        request.billing_period.as_deref(),
805    ).await {
806        Ok(cost_metric) => Ok(Json(cost_metric)),
807        Err(e) => Err((
808            Status::InternalServerError,
809            Json(json!({
810                "error": "Failed to create cost metric",
811                "message": format!("{}", e)
812            }))
813        )),
814    }
815}
816
817/// Delete a cost metric.
818#[delete("/platform/<platform_id>/cost_metrics/<id>")]
819pub async fn delete_cost_metric(
820    platform_id: i64,
821    id: i64,
822    db_manager: &State<Arc<DatabaseManager>>,
823) -> Result<Json<Value>, (Status, Json<Value>)> {
824    // Get platform information
825    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
826        Ok(platform) => platform,
827        Err(_) => {
828            return Err((
829                Status::NotFound,
830                Json(json!({
831                    "error": "Platform not found",
832                    "message": format!("Platform with ID {} does not exist", platform_id)
833                }))
834            ));
835        }
836    };
837
838    // Get platform-specific database pool
839    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
840        Ok(pool) => pool,
841        Err(_) => {
842            return Err((
843                Status::InternalServerError,
844                Json(json!({
845                    "error": "Database error",
846                    "message": "Failed to connect to platform database"
847                }))
848            ));
849        }
850    };
851
852    match db::cost::delete_cost_metric(&pool, id).await {
853        Ok(_) => Ok(Json(json!({ "status": "deleted" }))),
854        Err(e) => Err((
855            Status::InternalServerError,
856            Json(json!({
857                "error": "Failed to delete cost metric",
858                "message": format!("{}", e)
859            }))
860        )),
861    }
862}
863
864/// Get cost analysis by dimension (app, provider, resource_type, etc.)
865#[post("/platform/<platform_id>/cost_analysis/by_dimension", format = "json", data = "<request>")]
866pub async fn analyze_costs_by_dimension(
867    platform_id: i64,
868    request: Json<CostAnalysisByDimensionRequest>,
869    db_manager: &State<Arc<DatabaseManager>>,
870) -> Result<Json<Vec<(String, f64)>>, (Status, Json<Value>)> {
871    // Get platform information
872    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
873        Ok(platform) => platform,
874        Err(_) => {
875            return Err((
876                Status::NotFound,
877                Json(json!({
878                    "error": "Platform not found",
879                    "message": format!("Platform with ID {} does not exist", platform_id)
880                }))
881            ));
882        }
883    };
884
885    // Get platform-specific database pool
886    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
887        Ok(pool) => pool,
888        Err(_) => {
889            return Err((
890                Status::InternalServerError,
891                Json(json!({
892                    "error": "Database error",
893                    "message": "Failed to connect to platform database"
894                }))
895            ));
896        }
897    };
898
899    match db::cost::get_cost_metrics_by_dimension(
900        &pool,
901        &request.dimension,
902        request.start_date,
903        request.end_date,
904        request.limit,
905    ).await {
906        Ok(results) => Ok(Json(results)),
907        Err(e) => Err((
908            Status::InternalServerError,
909            Json(json!({
910                "error": "Failed to analyze costs by dimension",
911                "message": format!("{}", e)
912            }))
913        )),
914    }
915}
916
917/// Get application cost over time
918#[post("/platform/<platform_id>/cost_analysis/over_time", format = "json", data = "<request>")]
919pub async fn analyze_cost_over_time(
920    platform_id: i64,
921    request: Json<CostOverTimeRequest>,
922    db_manager: &State<Arc<DatabaseManager>>,
923) -> Result<Json<Vec<(DateTime<Utc>, f64)>>, (Status, Json<Value>)> {
924    // Get platform information
925    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
926        Ok(platform) => platform,
927        Err(_) => {
928            return Err((
929                Status::NotFound,
930                Json(json!({
931                    "error": "Platform not found",
932                    "message": format!("Platform with ID {} does not exist", platform_id)
933                }))
934            ));
935        }
936    };
937
938    // Get platform-specific database pool
939    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
940        Ok(pool) => pool,
941        Err(_) => {
942            return Err((
943                Status::InternalServerError,
944                Json(json!({
945                    "error": "Database error",
946                    "message": "Failed to connect to platform database"
947                }))
948            ));
949        }
950    };
951
952    match db::cost::get_app_cost_over_time(
953        &pool,
954        request.app_id,
955        &request.interval,
956        request.start_date,
957        request.end_date,
958    ).await {
959        Ok(results) => Ok(Json(results)),
960        Err(e) => Err((
961            Status::InternalServerError,
962            Json(json!({
963                "error": "Failed to analyze cost over time",
964                "message": format!("{}", e)
965            }))
966        )),
967    }
968}
969
970// Cost Budget Routes
971
972/// List all cost budgets with pagination support.
973#[get("/platform/<platform_id>/cost_budgets?<page>&<per_page>")]
974pub async fn list_cost_budgets(
975    platform_id: i64,
976    page: Option<i64>,
977    per_page: Option<i64>,
978    db_manager: &State<Arc<DatabaseManager>>,
979) -> Result<Json<Value>, (Status, Json<Value>)> {
980    // Get platform information
981    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
982        Ok(platform) => platform,
983        Err(_) => {
984            return Err((
985                Status::NotFound,
986                Json(json!({
987                    "error": "Platform not found",
988                    "message": format!("Platform with ID {} does not exist", platform_id)
989                }))
990            ));
991        }
992    };
993
994    // Get platform-specific database pool
995    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
996        Ok(pool) => pool,
997        Err(_) => {
998            return Err((
999                Status::InternalServerError,
1000                Json(json!({
1001                    "error": "Database error",
1002                    "message": "Failed to connect to platform database"
1003                }))
1004            ));
1005        }
1006    };
1007
1008    match (page, per_page) {
1009        (Some(p), Some(pp)) => {
1010            let cost_budgets = match db::cost::list_cost_budgets(&pool, p, pp).await {
1011                Ok(budgets) => budgets,
1012                Err(_) => {
1013                    return Err((
1014                        Status::InternalServerError,
1015                        Json(json!({
1016                            "error": "Database error",
1017                            "message": "Failed to retrieve cost budgets"
1018                        }))
1019                    ));
1020                }
1021            };
1022            
1023            let total_count = match db::cost::count_cost_budgets(&pool).await {
1024                Ok(count) => count,
1025                Err(_) => {
1026                    return Err((
1027                        Status::InternalServerError,
1028                        Json(json!({
1029                            "error": "Database error",
1030                            "message": "Failed to count cost budgets"
1031                        }))
1032                    ));
1033                }
1034            };
1035            
1036            let total_pages = (total_count as f64 / pp as f64).ceil() as i64;
1037
1038            let response = json!({
1039                "cost_budgets": cost_budgets,
1040                "pagination": {
1041                    "page": p,
1042                    "per_page": pp,
1043                    "total_count": total_count,
1044                    "total_pages": total_pages
1045                }
1046            });
1047
1048            Ok(Json(response))
1049        }
1050        _ => Err((
1051            Status::BadRequest,
1052            Json(json!({
1053                "error": "Missing pagination parameters",
1054                "message": "Please provide both 'page' and 'per_page' parameters"
1055            }))
1056        ))
1057    }
1058}
1059
1060/// Get a specific cost budget by ID.
1061#[get("/platform/<platform_id>/cost_budgets/<id>")]
1062pub async fn get_cost_budget(
1063    platform_id: i64,
1064    id: i64,
1065    db_manager: &State<Arc<DatabaseManager>>,
1066) -> Result<Json<CostBudget>, (Status, Json<Value>)> {
1067    // Get platform information
1068    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1069        Ok(platform) => platform,
1070        Err(_) => {
1071            return Err((
1072                Status::NotFound,
1073                Json(json!({
1074                    "error": "Platform not found",
1075                    "message": format!("Platform with ID {} does not exist", platform_id)
1076                }))
1077            ));
1078        }
1079    };
1080
1081    // Get platform-specific database pool
1082    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1083        Ok(pool) => pool,
1084        Err(_) => {
1085            return Err((
1086                Status::InternalServerError,
1087                Json(json!({
1088                    "error": "Database error",
1089                    "message": "Failed to connect to platform database"
1090                }))
1091            ));
1092        }
1093    };
1094
1095    match db::cost::get_cost_budget_by_id(&pool, id).await {
1096        Ok(budget) => Ok(Json(budget)),
1097        Err(_) => Err((
1098            Status::NotFound,
1099            Json(json!({
1100                "error": "Cost budget not found",
1101                "message": format!("Cost budget with ID {} could not be found", id)
1102            }))
1103        )),
1104    }
1105}
1106
1107/// Create a new cost budget.
1108#[post("/platform/<platform_id>/cost_budgets", format = "json", data = "<request>")]
1109pub async fn create_cost_budget(
1110    platform_id: i64,
1111    request: Json<CreateCostBudgetRequest>,
1112    db_manager: &State<Arc<DatabaseManager>>,
1113    user: User,
1114) -> Result<Json<CostBudget>, (Status, Json<Value>)> {
1115    // Get platform information
1116    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1117        Ok(platform) => platform,
1118        Err(_) => {
1119            return Err((
1120                Status::NotFound,
1121                Json(json!({
1122                    "error": "Platform not found",
1123                    "message": format!("Platform with ID {} does not exist", platform_id)
1124                }))
1125            ));
1126        }
1127    };
1128
1129    // Get platform-specific database pool
1130    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1131        Ok(pool) => pool,
1132        Err(_) => {
1133            return Err((
1134                Status::InternalServerError,
1135                Json(json!({
1136                    "error": "Database error",
1137                    "message": "Failed to connect to platform database"
1138                }))
1139            ));
1140        }
1141    };
1142
1143    let user_id = user.id;
1144
1145    //TODO: Validate user permissions here later
1146
1147    match db::cost::create_cost_budget(
1148        &pool,
1149        request.org_id,
1150        request.app_id,
1151        &request.budget_name,
1152        request.budget_amount,
1153        &request.currency,
1154        &request.budget_period,
1155        request.period_start,
1156        request.period_end,
1157        request.alert_threshold_percentage,
1158        &request.alert_contacts,
1159        user_id,
1160    ).await {
1161        Ok(budget) => Ok(Json(budget)),
1162        Err(e) => Err((
1163            Status::InternalServerError,
1164            Json(json!({
1165                "error": "Failed to create cost budget",
1166                "message": format!("{}", e)
1167            }))
1168        )),
1169    }
1170}
1171
1172/// Update an existing cost budget.
1173#[put("/platform/<platform_id>/cost_budgets/<id>", format = "json", data = "<request>")]
1174pub async fn update_cost_budget(
1175    platform_id: i64,
1176    id: i64,
1177    request: Json<UpdateCostBudgetRequest>,
1178    db_manager: &State<Arc<DatabaseManager>>,
1179) -> Result<Json<CostBudget>, (Status, Json<Value>)> {
1180    // Get platform information
1181    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1182        Ok(platform) => platform,
1183        Err(_) => {
1184            return Err((
1185                Status::NotFound,
1186                Json(json!({
1187                    "error": "Platform not found",
1188                    "message": format!("Platform with ID {} does not exist", platform_id)
1189                }))
1190            ));
1191        }
1192    };
1193
1194    // Get platform-specific database pool
1195    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1196        Ok(pool) => pool,
1197        Err(_) => {
1198            return Err((
1199                Status::InternalServerError,
1200                Json(json!({
1201                    "error": "Database error",
1202                    "message": "Failed to connect to platform database"
1203                }))
1204            ));
1205        }
1206    };
1207
1208    match db::cost::update_cost_budget(
1209        &pool,
1210        id,
1211        request.budget_name.as_deref(),
1212        request.budget_amount,
1213        request.alert_threshold_percentage,
1214        request.alert_contacts.as_deref(),
1215        request.is_active,
1216    ).await {
1217        Ok(budget) => Ok(Json(budget)),
1218        Err(e) => Err((
1219            Status::InternalServerError,
1220            Json(json!({
1221                "error": "Failed to update cost budget",
1222                "message": format!("{}", e)
1223            }))
1224        )),
1225    }
1226}
1227
1228/// Delete a cost budget.
1229#[delete("/platform/<platform_id>/cost_budgets/<id>")]
1230pub async fn delete_cost_budget(
1231    platform_id: i64,
1232    id: i64,
1233    db_manager: &State<Arc<DatabaseManager>>,
1234) -> Result<Json<Value>, (Status, Json<Value>)> {
1235    // Get platform information
1236    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1237        Ok(platform) => platform,
1238        Err(_) => {
1239            return Err((
1240                Status::NotFound,
1241                Json(json!({
1242                    "error": "Platform not found",
1243                    "message": format!("Platform with ID {} does not exist", platform_id)
1244                }))
1245            ));
1246        }
1247    };
1248
1249    // Get platform-specific database pool
1250    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1251        Ok(pool) => pool,
1252        Err(_) => {
1253            return Err((
1254                Status::InternalServerError,
1255                Json(json!({
1256                    "error": "Database error",
1257                    "message": "Failed to connect to platform database"
1258                }))
1259            ));
1260        }
1261    };
1262
1263    match db::cost::delete_cost_budget(&pool, id).await {
1264        Ok(_) => Ok(Json(json!({ "status": "deleted" }))),
1265        Err(e) => Err((
1266            Status::InternalServerError,
1267            Json(json!({
1268                "error": "Failed to delete cost budget",
1269                "message": format!("{}", e)
1270            }))
1271        )),
1272    }
1273}
1274
1275// Cost Projection Routes
1276
1277/// List all cost projections with pagination support.
1278#[get("/platform/<platform_id>/cost_projections?<page>&<per_page>")]
1279pub async fn list_cost_projections(
1280    platform_id: i64,
1281    page: Option<i64>,
1282    per_page: Option<i64>,
1283    db_manager: &State<Arc<DatabaseManager>>,
1284) -> Result<Json<Value>, (Status, Json<Value>)> {
1285    // Get platform information
1286    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1287        Ok(platform) => platform,
1288        Err(_) => {
1289            return Err((
1290                Status::NotFound,
1291                Json(json!({
1292                    "error": "Platform not found",
1293                    "message": format!("Platform with ID {} does not exist", platform_id)
1294                }))
1295            ));
1296        }
1297    };
1298
1299    // Get platform-specific database pool
1300    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1301        Ok(pool) => pool,
1302        Err(_) => {
1303            return Err((
1304                Status::InternalServerError,
1305                Json(json!({
1306                    "error": "Database error",
1307                    "message": "Failed to connect to platform database"
1308                }))
1309            ));
1310        }
1311    };
1312
1313    match (page, per_page) {
1314        (Some(p), Some(pp)) => {
1315            let projections = match db::cost::list_cost_projections(&pool, p, pp).await {
1316                Ok(projections) => projections,
1317                Err(_) => {
1318                    return Err((
1319                        Status::InternalServerError,
1320                        Json(json!({
1321                            "error": "Database error",
1322                            "message": "Failed to retrieve cost projections"
1323                        }))
1324                    ));
1325                }
1326            };
1327            
1328            let response = json!({
1329                "cost_projections": projections,
1330                "pagination": {
1331                    "page": p,
1332                    "per_page": pp
1333                }
1334            });
1335
1336            Ok(Json(response))
1337        }
1338        _ => Err((
1339            Status::BadRequest,
1340            Json(json!({
1341                "error": "Missing pagination parameters",
1342                "message": "Please provide both 'page' and 'per_page' parameters"
1343            }))
1344        ))
1345    }
1346}
1347
1348/// Get a specific cost projection by ID.
1349#[get("/platform/<platform_id>/cost_projections/<id>")]
1350pub async fn get_cost_projection(
1351    platform_id: i64,
1352    id: i64,
1353    db_manager: &State<Arc<DatabaseManager>>,
1354) -> Result<Json<CostProjection>, (Status, Json<Value>)> {
1355    // Get platform information
1356    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1357        Ok(platform) => platform,
1358        Err(_) => {
1359            return Err((
1360                Status::NotFound,
1361                Json(json!({
1362                    "error": "Platform not found",
1363                    "message": format!("Platform with ID {} does not exist", platform_id)
1364                }))
1365            ));
1366        }
1367    };
1368
1369    // Get platform-specific database pool
1370    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1371        Ok(pool) => pool,
1372        Err(_) => {
1373            return Err((
1374                Status::InternalServerError,
1375                Json(json!({
1376                    "error": "Database error",
1377                    "message": "Failed to connect to platform database"
1378                }))
1379            ));
1380        }
1381    };
1382
1383    match db::cost::get_cost_projection_by_id(&pool, id).await {
1384        Ok(projection) => Ok(Json(projection)),
1385        Err(_) => Err((
1386            Status::NotFound,
1387            Json(json!({
1388                "error": "Cost projection not found",
1389                "message": format!("Cost projection with ID {} could not be found", id)
1390            }))
1391        )),
1392    }
1393}
1394
1395/// Create a new cost projection.
1396#[post("/platform/<platform_id>/cost_projections", format = "json", data = "<request>")]
1397pub async fn create_cost_projection(
1398    platform_id: i64,
1399    request: Json<CreateCostProjectionRequest>,
1400    db_manager: &State<Arc<DatabaseManager>>,
1401) -> Result<Json<CostProjection>, (Status, Json<Value>)> {
1402    // Get platform information
1403    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1404        Ok(platform) => platform,
1405        Err(_) => {
1406            return Err((
1407                Status::NotFound,
1408                Json(json!({
1409                    "error": "Platform not found",
1410                    "message": format!("Platform with ID {} does not exist", platform_id)
1411                }))
1412            ));
1413        }
1414    };
1415
1416    // Get platform-specific database pool
1417    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1418        Ok(pool) => pool,
1419        Err(_) => {
1420            return Err((
1421                Status::InternalServerError,
1422                Json(json!({
1423                    "error": "Database error",
1424                    "message": "Failed to connect to platform database"
1425                }))
1426            ));
1427        }
1428    };
1429
1430    match db::cost::create_cost_projection(
1431        &pool,
1432        request.org_id,
1433        request.app_id,
1434        &request.projection_period,
1435        request.start_date,
1436        request.end_date,
1437        request.projected_cost,
1438        &request.currency,
1439        &request.projection_model,
1440        request.confidence_level,
1441        request.metadata.as_deref(),
1442    ).await {
1443        Ok(projection) => Ok(Json(projection)),
1444        Err(e) => Err((
1445            Status::InternalServerError,
1446            Json(json!({
1447                "error": "Failed to create cost projection",
1448                "message": format!("{}", e)
1449            }))
1450        )),
1451    }
1452}
1453
1454/// Delete a cost projection.
1455#[delete("/platform/<platform_id>/cost_projections/<id>")]
1456pub async fn delete_cost_projection(
1457    platform_id: i64,
1458    id: i64,
1459    db_manager: &State<Arc<DatabaseManager>>,
1460) -> Result<Json<Value>, (Status, Json<Value>)> {
1461    // Get platform information
1462    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1463        Ok(platform) => platform,
1464        Err(_) => {
1465            return Err((
1466                Status::NotFound,
1467                Json(json!({
1468                    "error": "Platform not found",
1469                    "message": format!("Platform with ID {} does not exist", platform_id)
1470                }))
1471            ));
1472        }
1473    };
1474
1475    // Get platform-specific database pool
1476    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1477        Ok(pool) => pool,
1478        Err(_) => {
1479            return Err((
1480                Status::InternalServerError,
1481                Json(json!({
1482                    "error": "Database error",
1483                    "message": "Failed to connect to platform database"
1484                }))
1485            ));
1486        }
1487    };
1488
1489    match db::cost::delete_cost_projection(&pool, id).await {
1490        Ok(_) => Ok(Json(json!({ "status": "deleted" }))),
1491        Err(e) => Err((
1492            Status::InternalServerError,
1493            Json(json!({
1494                "error": "Failed to delete cost projection",
1495                "message": format!("{}", e)
1496            }))
1497        )),
1498    }
1499}
1500
1501// Resource Pricing Routes
1502
1503/// List resource pricing with pagination and filtering support.
1504#[get("/platform/<platform_id>/resource_pricing?<page>&<per_page>&<resource_type_id>&<provider_id>&<region_id>&<pricing_model>&<tier_name>")]
1505pub async fn list_resource_pricing(
1506    platform_id: i64,
1507    page: Option<i64>,
1508    per_page: Option<i64>,
1509    resource_type_id: Option<i32>,
1510    provider_id: Option<i64>,
1511    region_id: Option<i64>,
1512    pricing_model: Option<String>,
1513    tier_name: Option<String>,
1514    db_manager: &State<Arc<DatabaseManager>>,
1515) -> Result<Json<Value>, (Status, Json<Value>)> {
1516    // Get platform information
1517    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1518        Ok(platform) => platform,
1519        Err(_) => {
1520            return Err((
1521                Status::NotFound,
1522                Json(json!({
1523                    "error": "Platform not found",
1524                    "message": format!("Platform with ID {} does not exist", platform_id)
1525                }))
1526            ));
1527        }
1528    };
1529
1530    // Get platform-specific database pool
1531    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1532        Ok(pool) => pool,
1533        Err(_) => {
1534            return Err((
1535                Status::InternalServerError,
1536                Json(json!({
1537                    "error": "Database error",
1538                    "message": "Failed to connect to platform database"
1539                }))
1540            ));
1541        }
1542    };
1543
1544    match (page, per_page) {
1545        (Some(p), Some(pp)) => {
1546            let pricing = match db::cost::list_resource_pricing(
1547                &pool, p, pp, resource_type_id, provider_id, region_id, pricing_model.as_deref(), tier_name.as_deref()
1548            ).await {
1549                Ok(pricing) => pricing,
1550                Err(_) => {
1551                    return Err((
1552                        Status::InternalServerError,
1553                        Json(json!({
1554                            "error": "Database error",
1555                            "message": "Failed to retrieve resource pricing"
1556                        }))
1557                    ));
1558                }
1559            };
1560            
1561            let response = json!({
1562                "resource_pricing": pricing,
1563                "pagination": {
1564                    "page": p,
1565                    "per_page": pp
1566                }
1567            });
1568
1569            Ok(Json(response))
1570        }
1571        _ => Err((
1572            Status::BadRequest,
1573            Json(json!({
1574                "error": "Missing pagination parameters",
1575                "message": "Please provide both 'page' and 'per_page' parameters"
1576            }))
1577        ))
1578    }
1579}
1580
1581/// Get a specific resource pricing entry by ID.
1582#[get("/platform/<platform_id>/resource_pricing/<id>")]
1583pub async fn get_resource_pricing(
1584    platform_id: i64,
1585    id: i64,
1586    db_manager: &State<Arc<DatabaseManager>>,
1587) -> Result<Json<ResourcePricing>, (Status, Json<Value>)> {
1588    // Get platform information
1589    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1590        Ok(platform) => platform,
1591        Err(_) => {
1592            return Err((
1593                Status::NotFound,
1594                Json(json!({
1595                    "error": "Platform not found",
1596                    "message": format!("Platform with ID {} does not exist", platform_id)
1597                }))
1598            ));
1599        }
1600    };
1601
1602    // Get platform-specific database pool
1603    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1604        Ok(pool) => pool,
1605        Err(_) => {
1606            return Err((
1607                Status::InternalServerError,
1608                Json(json!({
1609                    "error": "Database error",
1610                    "message": "Failed to connect to platform database"
1611                }))
1612            ));
1613        }
1614    };
1615
1616    match db::cost::get_resource_pricing_by_id(&pool, id).await {
1617        Ok(pricing) => Ok(Json(pricing)),
1618        Err(_) => Err((
1619            Status::NotFound,
1620            Json(json!({
1621                "error": "Resource pricing not found",
1622                "message": format!("Resource pricing with ID {} could not be found", id)
1623            }))
1624        )),
1625    }
1626}
1627
1628/// Create a new resource pricing entry.
1629#[post("/platform/<platform_id>/resource_pricing", format = "json", data = "<request>")]
1630pub async fn create_resource_pricing(
1631    platform_id: i64,
1632    request: Json<CreateResourcePricingRequest>,
1633    db_manager: &State<Arc<DatabaseManager>>,
1634) -> Result<Json<ResourcePricing>, (Status, Json<Value>)> {
1635    // Get platform information
1636    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1637        Ok(platform) => platform,
1638        Err(_) => {
1639            return Err((
1640                Status::NotFound,
1641                Json(json!({
1642                    "error": "Platform not found",
1643                    "message": format!("Platform with ID {} does not exist", platform_id)
1644                }))
1645            ));
1646        }
1647    };
1648
1649    // Get platform-specific database pool
1650    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1651        Ok(pool) => pool,
1652        Err(_) => {
1653            return Err((
1654                Status::InternalServerError,
1655                Json(json!({
1656                    "error": "Database error",
1657                    "message": "Failed to connect to platform database"
1658                }))
1659            ));
1660        }
1661    };
1662
1663    match db::cost::create_resource_pricing(
1664        &pool,
1665        request.resource_type_id,
1666        request.provider_id,
1667        request.region_id,
1668        &request.tier_name,
1669        request.unit_price,
1670        &request.currency,
1671        request.effective_from,
1672        request.effective_to,
1673        &request.pricing_model,
1674        request.commitment_period.as_deref(),
1675        request.volume_discount_tiers.as_deref(),
1676    ).await {
1677        Ok(pricing) => Ok(Json(pricing)),
1678        Err(e) => Err((
1679            Status::InternalServerError,
1680            Json(json!({
1681                "error": "Failed to create resource pricing",
1682                "message": format!("{}", e)
1683            }))
1684        )),
1685    }
1686}
1687
1688/// Update an existing resource pricing entry.
1689#[put("/platform/<platform_id>/resource_pricing/<id>", format = "json", data = "<request>")]
1690pub async fn update_resource_pricing(
1691    platform_id: i64,
1692    id: i64,
1693    request: Json<UpdateResourcePricingRequest>,
1694    db_manager: &State<Arc<DatabaseManager>>,
1695) -> Result<Json<ResourcePricing>, (Status, Json<Value>)> {
1696    // Get platform information
1697    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1698        Ok(platform) => platform,
1699        Err(_) => {
1700            return Err((
1701                Status::NotFound,
1702                Json(json!({
1703                    "error": "Platform not found",
1704                    "message": format!("Platform with ID {} does not exist", platform_id)
1705                }))
1706            ));
1707        }
1708    };
1709
1710    // Get platform-specific database pool
1711    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1712        Ok(pool) => pool,
1713        Err(_) => {
1714            return Err((
1715                Status::InternalServerError,
1716                Json(json!({
1717                    "error": "Database error",
1718                    "message": "Failed to connect to platform database"
1719                }))
1720            ));
1721        }
1722    };
1723
1724    match db::cost::update_resource_pricing(
1725        &pool,
1726        id,
1727        request.unit_price,
1728        request.effective_to,
1729        request.volume_discount_tiers.as_deref(),
1730    ).await {
1731        Ok(pricing) => Ok(Json(pricing)),
1732        Err(e) => Err((
1733            Status::InternalServerError,
1734            Json(json!({
1735                "error": "Failed to update resource pricing",
1736                "message": format!("{}", e)
1737            }))
1738        )),
1739    }
1740}
1741
1742/// Delete a resource pricing entry.
1743#[delete("/platform/<platform_id>/resource_pricing/<id>")]
1744pub async fn delete_resource_pricing(
1745    platform_id: i64,
1746    id: i64,
1747    db_manager: &State<Arc<DatabaseManager>>,
1748) -> Result<Json<Value>, (Status, Json<Value>)> {
1749    // Get platform information
1750    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1751        Ok(platform) => platform,
1752        Err(_) => {
1753            return Err((
1754                Status::NotFound,
1755                Json(json!({
1756                    "error": "Platform not found",
1757                    "message": format!("Platform with ID {} does not exist", platform_id)
1758                }))
1759            ));
1760        }
1761    };
1762
1763    // Get platform-specific database pool
1764    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1765        Ok(pool) => pool,
1766        Err(_) => {
1767            return Err((
1768                Status::InternalServerError,
1769                Json(json!({
1770                    "error": "Database error",
1771                    "message": "Failed to connect to platform database"
1772                }))
1773            ));
1774        }
1775    };
1776
1777    match db::cost::delete_resource_pricing(&pool, id).await {
1778        Ok(_) => Ok(Json(json!({ "status": "deleted" }))),
1779        Err(e) => Err((
1780            Status::InternalServerError,
1781            Json(json!({
1782                "error": "Failed to delete resource pricing",
1783                "message": format!("{}", e)
1784            }))
1785        )),
1786    }
1787}
1788
1789// Cost Allocation Tag Routes
1790
1791/// Get cost allocation tags for a specific resource.
1792#[get("/platform/<platform_id>/cost_allocation_tags/<resource_id>/<resource_type>")]
1793pub async fn get_cost_allocation_tags(
1794    platform_id: i64,
1795    resource_id: i64,
1796    resource_type: String,
1797    db_manager: &State<Arc<DatabaseManager>>,
1798) -> Result<Json<Vec<CostAllocationTag>>, (Status, Json<Value>)> {
1799    // Get platform information
1800    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1801        Ok(platform) => platform,
1802        Err(_) => {
1803            return Err((
1804                Status::NotFound,
1805                Json(json!({
1806                    "error": "Platform not found",
1807                    "message": format!("Platform with ID {} does not exist", platform_id)
1808                }))
1809            ));
1810        }
1811    };
1812
1813    // Get platform-specific database pool
1814    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1815        Ok(pool) => pool,
1816        Err(_) => {
1817            return Err((
1818                Status::InternalServerError,
1819                Json(json!({
1820                    "error": "Database error",
1821                    "message": "Failed to connect to platform database"
1822                }))
1823            ));
1824        }
1825    };
1826
1827    match db::cost::get_cost_allocation_tags(&pool, resource_id, &resource_type).await {
1828        Ok(tags) => Ok(Json(tags)),
1829        Err(e) => Err((
1830            Status::InternalServerError,
1831            Json(json!({
1832                "error": "Failed to retrieve cost allocation tags",
1833                "message": format!("{}", e)
1834            }))
1835        )),
1836    }
1837}
1838
1839/// Create a new cost allocation tag.
1840#[post("/platform/<platform_id>/cost_allocation_tags", format = "json", data = "<request>")]
1841pub async fn create_cost_allocation_tag(
1842    platform_id: i64,
1843    request: Json<CreateCostAllocationTagRequest>,
1844    db_manager: &State<Arc<DatabaseManager>>,
1845) -> Result<Json<CostAllocationTag>, (Status, Json<Value>)> {
1846    // Get platform information
1847    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1848        Ok(platform) => platform,
1849        Err(_) => {
1850            return Err((
1851                Status::NotFound,
1852                Json(json!({
1853                    "error": "Platform not found",
1854                    "message": format!("Platform with ID {} does not exist", platform_id)
1855                }))
1856            ));
1857        }
1858    };
1859
1860    // Get platform-specific database pool
1861    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1862        Ok(pool) => pool,
1863        Err(_) => {
1864            return Err((
1865                Status::InternalServerError,
1866                Json(json!({
1867                    "error": "Database error",
1868                    "message": "Failed to connect to platform database"
1869                }))
1870            ));
1871        }
1872    };
1873
1874    match db::cost::create_cost_allocation_tag(
1875        &pool,
1876        &request.tag_key,
1877        &request.tag_value,
1878        request.resource_id,
1879        &request.resource_type,
1880    ).await {
1881        Ok(tag) => Ok(Json(tag)),
1882        Err(e) => Err((
1883            Status::InternalServerError,
1884            Json(json!({
1885                "error": "Failed to create cost allocation tag",
1886                "message": format!("{}", e)
1887            }))
1888        )),
1889    }
1890}
1891
1892/// Delete a cost allocation tag.
1893#[delete("/platform/<platform_id>/cost_allocation_tags/<id>")]
1894pub async fn delete_cost_allocation_tag(
1895    platform_id: i64,
1896    id: i64,
1897    db_manager: &State<Arc<DatabaseManager>>,
1898) -> Result<Json<Value>, (Status, Json<Value>)> {
1899    // Get platform information
1900    let platform = match db::platforms::get_platform_by_id(db_manager.get_main_pool(), platform_id).await {
1901        Ok(platform) => platform,
1902        Err(_) => {
1903            return Err((
1904                Status::NotFound,
1905                Json(json!({
1906                    "error": "Platform not found",
1907                    "message": format!("Platform with ID {} does not exist", platform_id)
1908                }))
1909            ));
1910        }
1911    };
1912
1913    // Get platform-specific database pool
1914    let pool = match db_manager.get_platform_pool(&platform.name, platform_id).await {
1915        Ok(pool) => pool,
1916        Err(_) => {
1917            return Err((
1918                Status::InternalServerError,
1919                Json(json!({
1920                    "error": "Database error",
1921                    "message": "Failed to connect to platform database"
1922                }))
1923            ));
1924        }
1925    };
1926
1927    match db::cost::delete_cost_allocation_tag(&pool, id).await {
1928        Ok(_) => Ok(Json(json!({ "status": "deleted" }))),
1929        Err(e) => Err((
1930            Status::InternalServerError,
1931            Json(json!({
1932                "error": "Failed to delete cost allocation tag",
1933                "message": format!("{}", e)
1934            }))
1935        )),
1936    }
1937}