scouter_sql/sql/
schema.rs

1use chrono::{DateTime, Utc};
2use scouter_types::psi::DistributionData;
3use scouter_types::{
4    alert::Alert,
5    custom::{BinnedCustomMetric, BinnedCustomMetricStats},
6    get_utc_datetime,
7    psi::FeatureBinProportionResult,
8    RecordType,
9};
10use serde::{Deserialize, Serialize};
11use sqlx::{postgres::PgRow, Error, FromRow, Row};
12use std::collections::BTreeMap;
13use std::collections::HashMap;
14
15#[derive(Serialize, Deserialize, Debug, Clone)]
16pub struct DriftRecord {
17    pub created_at: DateTime<Utc>,
18    pub name: String,
19    pub space: String,
20    pub version: String,
21    pub feature: String,
22    pub value: f64,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct SpcFeatureResult {
27    pub feature: String,
28    pub created_at: Vec<DateTime<Utc>>,
29    pub values: Vec<f64>,
30}
31
32impl<'r> FromRow<'r, PgRow> for SpcFeatureResult {
33    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
34        Ok(SpcFeatureResult {
35            feature: row.try_get("feature")?,
36            created_at: row.try_get("created_at")?,
37            values: row.try_get("values")?,
38        })
39    }
40}
41
42#[derive(Debug)]
43pub struct FeatureDistributionWrapper(pub String, pub DistributionData);
44
45impl<'r> FromRow<'r, PgRow> for FeatureDistributionWrapper {
46    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
47        let feature: String = row.try_get("feature")?;
48        let sample_size: i64 = row.try_get("sample_size")?;
49        let bins_json: serde_json::Value = row.try_get("bins")?;
50        let bins: BTreeMap<usize, f64> =
51            serde_json::from_value(bins_json).map_err(|e| Error::Decode(e.into()))?;
52
53        Ok(FeatureDistributionWrapper(
54            feature,
55            DistributionData {
56                sample_size: sample_size as u64,
57                bins,
58            },
59        ))
60    }
61}
62
63pub struct BinnedCustomMetricWrapper(pub BinnedCustomMetric);
64
65impl<'r> FromRow<'r, PgRow> for BinnedCustomMetricWrapper {
66    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
67        let stats_json: Vec<serde_json::Value> = row.try_get("stats")?;
68
69        let stats: Vec<BinnedCustomMetricStats> = stats_json
70            .into_iter()
71            .map(|value| serde_json::from_value(value).unwrap_or_default())
72            .collect();
73
74        Ok(BinnedCustomMetricWrapper(BinnedCustomMetric {
75            metric: row.try_get("metric")?,
76            created_at: row.try_get("created_at")?,
77            stats,
78        }))
79    }
80}
81
82pub struct AlertWrapper(pub Alert);
83
84impl<'r> FromRow<'r, PgRow> for AlertWrapper {
85    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
86        let alert_value: serde_json::Value = row.try_get("alert")?;
87        let alert: BTreeMap<String, String> =
88            serde_json::from_value(alert_value).unwrap_or_default();
89
90        Ok(AlertWrapper(Alert {
91            created_at: row.try_get("created_at")?,
92            name: row.try_get("name")?,
93            space: row.try_get("space")?,
94            version: row.try_get("version")?,
95            alert,
96            entity_name: row.try_get("entity_name")?,
97            id: row.try_get("id")?,
98            drift_type: row.try_get("drift_type")?,
99            active: row.try_get("active")?,
100        }))
101    }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct TaskRequest {
106    pub name: String,
107    pub space: String,
108    pub version: String,
109    pub profile: String,
110    pub drift_type: String,
111    pub previous_run: DateTime<Utc>,
112    pub schedule: String,
113    pub uid: String,
114}
115
116impl<'r> FromRow<'r, PgRow> for TaskRequest {
117    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
118        let profile: serde_json::Value = row.try_get("profile")?;
119
120        Ok(TaskRequest {
121            name: row.try_get("name")?,
122            space: row.try_get("space")?,
123            version: row.try_get("version")?,
124            profile: profile.to_string(),
125            drift_type: row.try_get("drift_type")?,
126            previous_run: row.try_get("previous_run")?,
127            schedule: row.try_get("schedule")?,
128            uid: row.try_get("uid")?,
129        })
130    }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct ObservabilityResult {
135    pub route_name: String,
136    pub created_at: Vec<DateTime<Utc>>,
137    pub p5: Vec<f64>,
138    pub p25: Vec<f64>,
139    pub p50: Vec<f64>,
140    pub p95: Vec<f64>,
141    pub p99: Vec<f64>,
142    pub total_request_count: Vec<i64>,
143    pub total_error_count: Vec<i64>,
144    pub error_latency: Vec<f64>,
145    pub status_counts: Vec<HashMap<String, i64>>,
146}
147
148impl<'r> FromRow<'r, PgRow> for ObservabilityResult {
149    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
150        // decode status counts to vec of jsonb
151        let status_counts: Vec<serde_json::Value> = row.try_get("status_counts")?;
152
153        // convert vec of jsonb to vec of hashmaps
154        let status_counts: Vec<HashMap<String, i64>> = status_counts
155            .into_iter()
156            .map(|value| serde_json::from_value(value).unwrap_or_default())
157            .collect();
158
159        Ok(ObservabilityResult {
160            route_name: row.try_get("route_name")?,
161            created_at: row.try_get("created_at")?,
162            p5: row.try_get("p5")?,
163            p25: row.try_get("p25")?,
164            p50: row.try_get("p50")?,
165            p95: row.try_get("p95")?,
166            p99: row.try_get("p99")?,
167            total_request_count: row.try_get("total_request_count")?,
168            total_error_count: row.try_get("total_error_count")?,
169            error_latency: row.try_get("error_latency")?,
170            status_counts,
171        })
172    }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct BinProportion {
177    pub bin_id: usize,
178    pub proportion: f64,
179}
180
181#[derive(Debug)]
182pub struct FeatureBinProportionResultWrapper(pub FeatureBinProportionResult);
183
184impl<'r> FromRow<'r, PgRow> for FeatureBinProportionResultWrapper {
185    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
186        // Extract the bin_proportions as a Vec of tuples
187        let bin_proportions_json: Vec<serde_json::Value> = row.try_get("bin_proportions")?;
188
189        // Convert the Vec of tuples into a Vec of BinProportion structs
190        let bin_proportions: Vec<BTreeMap<usize, f64>> = bin_proportions_json
191            .into_iter()
192            .map(|json| serde_json::from_value(json).unwrap_or_default())
193            .collect();
194
195        let overall_proportions_json: serde_json::Value = row.try_get("overall_proportions")?;
196        let overall_proportions: BTreeMap<usize, f64> =
197            serde_json::from_value(overall_proportions_json).unwrap_or_default();
198
199        Ok(FeatureBinProportionResultWrapper(
200            FeatureBinProportionResult {
201                feature: row.try_get("feature")?,
202                created_at: row.try_get("created_at")?,
203                bin_proportions,
204                overall_proportions,
205            },
206        ))
207    }
208}
209#[derive(Debug, Clone, FromRow)]
210pub struct Entity {
211    pub space: String,
212    pub name: String,
213    pub version: String,
214    pub begin_timestamp: DateTime<Utc>,
215    pub end_timestamp: DateTime<Utc>,
216}
217
218impl Entity {
219    pub fn get_write_path(&self, record_type: &RecordType) -> String {
220        format!(
221            "{}/{}/{}/{}",
222            self.space, self.name, self.version, record_type
223        )
224    }
225}
226
227#[derive(Debug, Serialize, Deserialize, Clone)]
228pub struct User {
229    pub id: Option<i32>,
230    pub created_at: DateTime<Utc>,
231    pub active: bool,
232    pub username: String,
233    pub password_hash: String,
234    pub hashed_recovery_codes: Vec<String>,
235    pub permissions: Vec<String>,
236    pub group_permissions: Vec<String>,
237    pub role: String,
238    pub favorite_spaces: Vec<String>,
239    pub refresh_token: Option<String>,
240    pub email: String,
241    pub updated_at: DateTime<Utc>,
242}
243
244impl User {
245    #[allow(clippy::too_many_arguments)]
246    pub fn new(
247        username: String,
248        password_hash: String,
249        email: String,
250        hashed_recovery_codes: Vec<String>,
251        permissions: Option<Vec<String>>,
252        group_permissions: Option<Vec<String>>,
253        role: Option<String>,
254        favorite_spaces: Option<Vec<String>>,
255    ) -> Self {
256        let created_at = get_utc_datetime();
257
258        User {
259            id: None,
260            created_at,
261            active: true,
262            username,
263            password_hash,
264            hashed_recovery_codes,
265            permissions: permissions.unwrap_or(vec!["read:all".to_string()]),
266            group_permissions: group_permissions.unwrap_or(vec!["user".to_string()]),
267            favorite_spaces: favorite_spaces.unwrap_or_default(),
268            role: role.unwrap_or("user".to_string()),
269            refresh_token: None,
270            email,
271            updated_at: created_at,
272        }
273    }
274}
275
276impl FromRow<'_, PgRow> for User {
277    fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
278        let id = row.try_get("id")?;
279        let created_at = row.try_get("created_at")?;
280        let updated_at = row.try_get("updated_at")?;
281        let active = row.try_get("active")?;
282        let username = row.try_get("username")?;
283        let password_hash = row.try_get("password_hash")?;
284        let email = row.try_get("email")?;
285        let role = row.try_get("role")?;
286        let refresh_token = row.try_get("refresh_token")?;
287
288        let group_permissions: Vec<String> =
289            serde_json::from_value(row.try_get("group_permissions")?).unwrap_or_default();
290
291        let permissions: Vec<String> =
292            serde_json::from_value(row.try_get("permissions")?).unwrap_or_default();
293
294        let hashed_recovery_codes: Vec<String> =
295            serde_json::from_value(row.try_get("hashed_recovery_codes")?).unwrap_or_default();
296
297        let favorite_spaces: Vec<String> =
298            serde_json::from_value(row.try_get("favorite_spaces")?).unwrap_or_default();
299
300        Ok(User {
301            id,
302            created_at,
303            updated_at,
304            active,
305            username,
306            password_hash,
307            email,
308            role,
309            refresh_token,
310            hashed_recovery_codes,
311            permissions,
312            group_permissions,
313            favorite_spaces,
314        })
315    }
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
319pub struct UpdateAlertResult {
320    pub id: i32,
321    pub active: bool,
322    pub updated_at: DateTime<Utc>,
323}