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