shuttle_common/models/
project.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use strum::EnumString;
4
5#[cfg(feature = "display")]
6use crossterm::style::Stylize;
7#[cfg(feature = "display")]
8use std::fmt::Write;
9
10use super::deployment::DeploymentState;
11
12#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
13#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
14#[typeshare::typeshare]
15pub struct ProjectCreateRequest {
16    #[cfg_attr(feature = "utoipa", schema(pattern = "^[a-z0-9-]{1,32}$"))]
17    pub name: String,
18}
19
20#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
21#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
22#[typeshare::typeshare]
23pub struct ProjectResponse {
24    pub id: String,
25    /// Display name
26    pub name: String,
27    /// Project owner
28    pub user_id: String,
29    /// Team project belongs to
30    pub team_id: Option<String>,
31    pub created_at: DateTime<Utc>,
32    pub compute_tier: Option<ComputeTier>,
33    /// State of the current deployment if one exists (something has been deployed).
34    pub deployment_state: Option<DeploymentState>,
35    /// URIs where running deployments can be reached
36    pub uris: Vec<String>,
37}
38
39impl ProjectResponse {
40    #[cfg(feature = "display")]
41    pub fn to_string_colored(&self) -> String {
42        let mut s = String::new();
43        writeln!(&mut s, "{}", "Project info:".bold()).unwrap();
44        writeln!(&mut s, "  Project ID: {}", self.id).unwrap();
45        writeln!(&mut s, "  Project Name: {}", self.name).unwrap();
46        writeln!(&mut s, "  Owner: {}", self.user_id).unwrap();
47        writeln!(
48            &mut s,
49            "  Team: {}",
50            self.team_id.as_deref().unwrap_or("N/A")
51        )
52        .unwrap();
53        writeln!(
54            &mut s,
55            "  Created: {}",
56            self.created_at
57                .to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
58        )
59        .unwrap();
60        writeln!(&mut s, "  URIs:").unwrap();
61        for uri in &self.uris {
62            writeln!(&mut s, "    - {uri}").unwrap();
63        }
64
65        // Display compute tier information if configured
66        if let Some(compute_tier) = &self.compute_tier {
67            writeln!(
68                &mut s,
69                "  Instance size: {}",
70                compute_tier.to_fancy_string()
71            )
72            .unwrap_or_default();
73        }
74
75        s
76    }
77}
78
79#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
80#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
81#[typeshare::typeshare]
82pub struct ProjectListResponse {
83    pub projects: Vec<ProjectResponse>,
84}
85
86/// Set wanted field(s) to Some to update those parts of the project
87#[derive(Debug, Default, Deserialize, Serialize, Clone)]
88#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
89#[typeshare::typeshare]
90pub struct ProjectUpdateRequest {
91    /// Change display name
92    #[cfg_attr(feature = "utoipa", schema(pattern = "^[a-z0-9-]{1,32}$"))]
93    pub name: Option<String>,
94    /// Transfer to other user
95    #[cfg_attr(feature = "utoipa", schema(pattern = "^user_[A-Z0-9]{26}$"))]
96    pub user_id: Option<String>,
97    /// Transfer to a team
98    #[cfg_attr(feature = "utoipa", schema(pattern = "^team_[A-Z0-9]{26}$"))]
99    pub team_id: Option<String>,
100    /// Transfer away from current team
101    pub remove_from_team: Option<bool>,
102    /// Project runtime configuration
103    pub config: Option<serde_json::Value>,
104}
105
106#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, EnumString)]
107#[serde(rename_all = "lowercase")]
108#[strum(serialize_all = "lowercase")]
109#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
110#[typeshare::typeshare]
111pub enum ComputeTier {
112    #[default]
113    XS,
114    S,
115    M,
116    L,
117    XL,
118    XXL,
119
120    /// Forward compatibility
121    #[cfg(feature = "unknown-variants")]
122    #[doc(hidden)]
123    #[typeshare(skip)]
124    #[serde(untagged, skip_serializing)]
125    #[strum(default)]
126    Unknown(String),
127}
128impl ComputeTier {
129    pub fn to_fancy_string(&self) -> String {
130        match self {
131            Self::XS => "Basic (0.25 vCPU, 0.5 GB RAM)".to_owned(),
132            Self::S => "Small (0.5 vCPU, 1 GB RAM)".to_owned(),
133            Self::M => "Medium (1 vCPU, 2 GB RAM)".to_owned(),
134            Self::L => "Large (2 vCPU, 4 GB RAM)".to_owned(),
135            Self::XL => "X Large (4 vCPU, 8 GB RAM)".to_owned(),
136            Self::XXL => "XX Large (8 vCPU, 16 GB RAM)".to_owned(),
137            #[cfg(feature = "unknown-variants")]
138            Self::Unknown(s) => format!("Unknown: {s}"),
139        }
140    }
141}
142
143/// Sub-Response for the /user/me/usage backend endpoint
144#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
145#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
146#[typeshare::typeshare]
147pub struct ProjectUsageResponse {
148    /// Show the build minutes clocked against this Project.
149    pub build_minutes: ProjectUsageBuild,
150
151    /// Show the VCPU used by this project on the container platform.
152    pub vcpu: ProjectUsageVCPU,
153
154    /// Daily usage breakdown for this project
155    pub daily: Vec<ProjectUsageDaily>,
156}
157
158/// Build Minutes subquery for the [`ProjectUsageResponse`] struct
159#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)]
160#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
161#[typeshare::typeshare]
162pub struct ProjectUsageBuild {
163    /// Number of build minutes used by this project.
164    pub used: u32,
165
166    /// Limit of build minutes for this project, before additional charges are liable.
167    pub limit: u32,
168}
169
170/// VCPU subquery for the [`ProjectUsageResponse`] struct
171#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
172#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
173#[typeshare::typeshare]
174pub struct ProjectUsageVCPU {
175    /// Used reserved VCPU hours for a project.
176    pub reserved_hours: f32,
177
178    /// Used VCPU hours beyond the included reserved VCPU hours for a project.
179    pub billable_hours: f32,
180}
181
182// Add this new struct for daily usage data
183#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
184#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
185#[typeshare::typeshare]
186pub struct ProjectUsageDaily {
187    pub avg_cpu_utilised: f32,
188    pub avg_mem_utilised: f32,
189    pub billable_vcpu_hours: f32,
190    pub build_minutes: u32,
191    pub isodate: chrono::NaiveDate,
192    pub max_cpu_reserved: f32,
193    pub max_mem_reserved: f32,
194    pub min_cpu_reserved: f32,
195    pub min_mem_reserved: f32,
196    pub reserved_vcpu_hours: f32,
197    pub runtime_minutes: u32,
198}