shared/models/
nest_egg_variable.rs

1use crate::{
2    models::{InsertQueryBuilder, UpdateQueryBuilder},
3    prelude::*,
4};
5use garde::Validate;
6use serde::{Deserialize, Serialize};
7use sqlx::{Row, postgres::PgRow};
8use std::{
9    collections::BTreeMap,
10    sync::{Arc, LazyLock},
11};
12use utoipa::ToSchema;
13
14#[derive(ToSchema, Validate, Serialize, Deserialize, Clone)]
15pub struct ExportedNestEggVariable {
16    #[garde(length(chars, min = 1, max = 255))]
17    #[schema(min_length = 1, max_length = 255)]
18    pub name: compact_str::CompactString,
19    #[garde(length(max = 1024))]
20    #[schema(max_length = 1024)]
21    pub description: Option<compact_str::CompactString>,
22    #[garde(skip)]
23    #[serde(default, alias = "sort")]
24    pub order: i16,
25
26    #[garde(length(chars, min = 1, max = 255))]
27    #[schema(min_length = 1, max_length = 255)]
28    pub env_variable: compact_str::CompactString,
29    #[garde(length(max = 1024))]
30    #[schema(max_length = 1024)]
31    #[serde(
32        default,
33        deserialize_with = "crate::deserialize::deserialize_stringable_option"
34    )]
35    pub default_value: Option<String>,
36
37    #[garde(skip)]
38    pub user_viewable: bool,
39    #[garde(skip)]
40    pub user_editable: bool,
41    #[garde(skip)]
42    #[serde(default)]
43    pub secret: bool,
44    #[garde(skip)]
45    #[serde(
46        default,
47        deserialize_with = "crate::deserialize::deserialize_nest_egg_variable_rules"
48    )]
49    pub rules: Vec<compact_str::CompactString>,
50}
51
52#[derive(Serialize, Deserialize)]
53pub struct NestEggVariable {
54    pub uuid: uuid::Uuid,
55
56    pub name: compact_str::CompactString,
57    pub description: Option<compact_str::CompactString>,
58    pub order: i16,
59
60    pub env_variable: compact_str::CompactString,
61    pub default_value: Option<String>,
62    pub user_viewable: bool,
63    pub user_editable: bool,
64    pub secret: bool,
65    pub rules: Vec<compact_str::CompactString>,
66
67    pub created: chrono::NaiveDateTime,
68}
69
70impl BaseModel for NestEggVariable {
71    const NAME: &'static str = "nest_egg_variable";
72
73    #[inline]
74    fn columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
75        let prefix = prefix.unwrap_or_default();
76
77        BTreeMap::from([
78            (
79                "nest_egg_variables.uuid",
80                compact_str::format_compact!("{prefix}uuid"),
81            ),
82            (
83                "nest_egg_variables.name",
84                compact_str::format_compact!("{prefix}name"),
85            ),
86            (
87                "nest_egg_variables.description",
88                compact_str::format_compact!("{prefix}description"),
89            ),
90            (
91                "nest_egg_variables.order_",
92                compact_str::format_compact!("{prefix}order"),
93            ),
94            (
95                "nest_egg_variables.env_variable",
96                compact_str::format_compact!("{prefix}env_variable"),
97            ),
98            (
99                "nest_egg_variables.default_value",
100                compact_str::format_compact!("{prefix}default_value"),
101            ),
102            (
103                "nest_egg_variables.user_viewable",
104                compact_str::format_compact!("{prefix}user_viewable"),
105            ),
106            (
107                "nest_egg_variables.user_editable",
108                compact_str::format_compact!("{prefix}user_editable"),
109            ),
110            (
111                "nest_egg_variables.secret",
112                compact_str::format_compact!("{prefix}secret"),
113            ),
114            (
115                "nest_egg_variables.rules",
116                compact_str::format_compact!("{prefix}rules"),
117            ),
118            (
119                "nest_egg_variables.created",
120                compact_str::format_compact!("{prefix}created"),
121            ),
122        ])
123    }
124
125    #[inline]
126    fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
127        let prefix = prefix.unwrap_or_default();
128
129        Ok(Self {
130            uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
131            name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
132            description: row
133                .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
134            order: row.try_get(compact_str::format_compact!("{prefix}order").as_str())?,
135            env_variable: row
136                .try_get(compact_str::format_compact!("{prefix}env_variable").as_str())?,
137            default_value: row
138                .try_get(compact_str::format_compact!("{prefix}default_value").as_str())?,
139            user_viewable: row
140                .try_get(compact_str::format_compact!("{prefix}user_viewable").as_str())?,
141            user_editable: row
142                .try_get(compact_str::format_compact!("{prefix}user_editable").as_str())?,
143            secret: row.try_get(compact_str::format_compact!("{prefix}secret").as_str())?,
144            rules: row.try_get(compact_str::format_compact!("{prefix}rules").as_str())?,
145            created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
146        })
147    }
148}
149
150impl NestEggVariable {
151    pub async fn by_egg_uuid_uuid(
152        database: &crate::database::Database,
153        egg_uuid: uuid::Uuid,
154        uuid: uuid::Uuid,
155    ) -> Result<Option<Self>, crate::database::DatabaseError> {
156        let row = sqlx::query(&format!(
157            r#"
158            SELECT {}
159            FROM nest_egg_variables
160            WHERE nest_egg_variables.egg_uuid = $1 AND nest_egg_variables.uuid = $2
161            "#,
162            Self::columns_sql(None)
163        ))
164        .bind(egg_uuid)
165        .bind(uuid)
166        .fetch_optional(database.read())
167        .await?;
168
169        row.try_map(|row| Self::map(None, &row))
170    }
171
172    pub async fn all_by_egg_uuid(
173        database: &crate::database::Database,
174        egg_uuid: uuid::Uuid,
175    ) -> Result<Vec<Self>, crate::database::DatabaseError> {
176        let rows = sqlx::query(&format!(
177            r#"
178            SELECT {}
179            FROM nest_egg_variables
180            WHERE nest_egg_variables.egg_uuid = $1
181            ORDER BY nest_egg_variables.order_, nest_egg_variables.created
182            "#,
183            Self::columns_sql(None)
184        ))
185        .bind(egg_uuid)
186        .fetch_all(database.read())
187        .await?;
188
189        rows.into_iter()
190            .map(|row| Self::map(None, &row))
191            .try_collect_vec()
192    }
193
194    #[inline]
195    pub fn into_exported(self) -> ExportedNestEggVariable {
196        ExportedNestEggVariable {
197            name: self.name,
198            description: self.description,
199            order: self.order,
200            env_variable: self.env_variable,
201            default_value: self.default_value,
202            user_viewable: self.user_viewable,
203            user_editable: self.user_editable,
204            secret: self.secret,
205            rules: self.rules,
206        }
207    }
208
209    #[inline]
210    pub fn into_admin_api_object(self) -> AdminApiNestEggVariable {
211        AdminApiNestEggVariable {
212            uuid: self.uuid,
213            name: self.name,
214            description: self.description,
215            order: self.order,
216            env_variable: self.env_variable,
217            default_value: self.default_value,
218            user_viewable: self.user_viewable,
219            user_editable: self.user_editable,
220            is_secret: self.secret,
221            rules: self.rules,
222            created: self.created.and_utc(),
223        }
224    }
225}
226
227#[derive(ToSchema, Deserialize, Validate)]
228pub struct CreateNestEggVariableOptions {
229    #[garde(skip)]
230    pub egg_uuid: uuid::Uuid,
231
232    #[garde(length(chars, min = 3, max = 255))]
233    #[schema(min_length = 3, max_length = 255)]
234    pub name: compact_str::CompactString,
235
236    #[garde(length(chars, min = 1, max = 1024))]
237    #[schema(min_length = 1, max_length = 1024)]
238    pub description: Option<compact_str::CompactString>,
239
240    #[garde(skip)]
241    pub order: i16,
242
243    #[garde(length(chars, min = 1, max = 255))]
244    #[schema(min_length = 1, max_length = 255)]
245    pub env_variable: compact_str::CompactString,
246
247    #[garde(length(max = 1024))]
248    #[schema(max_length = 1024)]
249    pub default_value: Option<String>,
250
251    #[garde(skip)]
252    pub user_viewable: bool,
253    #[garde(skip)]
254    pub user_editable: bool,
255    #[garde(skip)]
256    pub secret: bool,
257
258    #[garde(custom(rule_validator::validate_rules))]
259    pub rules: Vec<compact_str::CompactString>,
260}
261
262#[async_trait::async_trait]
263impl CreatableModel for NestEggVariable {
264    type CreateOptions<'a> = CreateNestEggVariableOptions;
265    type CreateResult = Self;
266
267    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
268        static CREATE_LISTENERS: LazyLock<CreateListenerList<NestEggVariable>> =
269            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
270
271        &CREATE_LISTENERS
272    }
273
274    async fn create(
275        state: &crate::State,
276        mut options: Self::CreateOptions<'_>,
277    ) -> Result<Self::CreateResult, crate::database::DatabaseError> {
278        options.validate()?;
279
280        let mut transaction = state.database.write().begin().await?;
281
282        let mut query_builder = InsertQueryBuilder::new("nest_egg_variables");
283
284        Self::run_create_handlers(&mut options, &mut query_builder, state, &mut transaction)
285            .await?;
286
287        query_builder
288            .set("egg_uuid", options.egg_uuid)
289            .set("name", &options.name)
290            .set("description", &options.description)
291            .set("order_", options.order)
292            .set("env_variable", &options.env_variable)
293            .set("default_value", &options.default_value)
294            .set("user_viewable", options.user_viewable)
295            .set("user_editable", options.user_editable)
296            .set("secret", options.secret)
297            .set("rules", &options.rules);
298
299        let row = query_builder
300            .returning(&Self::columns_sql(None))
301            .fetch_one(&mut *transaction)
302            .await?;
303        let nest_egg_variable = Self::map(None, &row)?;
304
305        transaction.commit().await?;
306
307        Ok(nest_egg_variable)
308    }
309}
310
311#[derive(ToSchema, Serialize, Deserialize, Validate, Default)]
312pub struct UpdateNestEggVariableOptions {
313    #[garde(length(chars, min = 3, max = 255))]
314    #[schema(min_length = 3, max_length = 255)]
315    pub name: Option<compact_str::CompactString>,
316
317    #[garde(length(chars, min = 1, max = 1024))]
318    #[schema(min_length = 1, max_length = 1024)]
319    #[serde(
320        default,
321        skip_serializing_if = "Option::is_none",
322        with = "::serde_with::rust::double_option"
323    )]
324    pub description: Option<Option<compact_str::CompactString>>,
325
326    #[garde(skip)]
327    pub order: Option<i16>,
328
329    #[garde(length(chars, min = 1, max = 255))]
330    #[schema(min_length = 1, max_length = 255)]
331    pub env_variable: Option<compact_str::CompactString>,
332
333    #[garde(length(max = 1024))]
334    #[schema(max_length = 1024)]
335    #[serde(
336        default,
337        skip_serializing_if = "Option::is_none",
338        with = "::serde_with::rust::double_option"
339    )]
340    pub default_value: Option<Option<String>>,
341
342    #[garde(skip)]
343    pub user_viewable: Option<bool>,
344    #[garde(skip)]
345    pub user_editable: Option<bool>,
346    #[garde(skip)]
347    pub secret: Option<bool>,
348
349    #[garde(inner(custom(rule_validator::validate_rules)))]
350    pub rules: Option<Vec<compact_str::CompactString>>,
351}
352
353#[async_trait::async_trait]
354impl UpdatableModel for NestEggVariable {
355    type UpdateOptions = UpdateNestEggVariableOptions;
356
357    fn get_update_handlers() -> &'static LazyLock<UpdateListenerList<Self>> {
358        static UPDATE_LISTENERS: LazyLock<UpdateListenerList<NestEggVariable>> =
359            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
360
361        &UPDATE_LISTENERS
362    }
363
364    async fn update(
365        &mut self,
366        state: &crate::State,
367        mut options: Self::UpdateOptions,
368    ) -> Result<(), crate::database::DatabaseError> {
369        options.validate()?;
370
371        let mut transaction = state.database.write().begin().await?;
372
373        let mut query_builder = UpdateQueryBuilder::new("nest_egg_variables");
374
375        Self::run_update_handlers(
376            self,
377            &mut options,
378            &mut query_builder,
379            state,
380            &mut transaction,
381        )
382        .await?;
383
384        query_builder
385            .set("name", options.name.as_ref())
386            .set(
387                "description",
388                options.description.as_ref().map(|d| d.as_ref()),
389            )
390            .set("order_", options.order)
391            .set("env_variable", options.env_variable.as_ref())
392            .set(
393                "default_value",
394                options.default_value.as_ref().map(|d| d.as_ref()),
395            )
396            .set("user_viewable", options.user_viewable)
397            .set("user_editable", options.user_editable)
398            .set("secret", options.secret)
399            .set("rules", options.rules.as_ref())
400            .where_eq("uuid", self.uuid);
401
402        query_builder.execute(&mut *transaction).await?;
403
404        if let Some(name) = options.name {
405            self.name = name;
406        }
407        if let Some(description) = options.description {
408            self.description = description;
409        }
410        if let Some(order) = options.order {
411            self.order = order;
412        }
413        if let Some(env_variable) = options.env_variable {
414            self.env_variable = env_variable;
415        }
416        if let Some(default_value) = options.default_value {
417            self.default_value = default_value;
418        }
419        if let Some(user_viewable) = options.user_viewable {
420            self.user_viewable = user_viewable;
421        }
422        if let Some(user_editable) = options.user_editable {
423            self.user_editable = user_editable;
424        }
425        if let Some(secret) = options.secret {
426            self.secret = secret;
427        }
428        if let Some(rules) = options.rules {
429            self.rules = rules;
430        }
431
432        transaction.commit().await?;
433
434        Ok(())
435    }
436}
437
438#[async_trait::async_trait]
439impl DeletableModel for NestEggVariable {
440    type DeleteOptions = ();
441
442    fn get_delete_handlers() -> &'static LazyLock<DeleteListenerList<Self>> {
443        static DELETE_LISTENERS: LazyLock<DeleteListenerList<NestEggVariable>> =
444            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
445
446        &DELETE_LISTENERS
447    }
448
449    async fn delete(
450        &self,
451        state: &crate::State,
452        options: Self::DeleteOptions,
453    ) -> Result<(), anyhow::Error> {
454        let mut transaction = state.database.write().begin().await?;
455
456        self.run_delete_handlers(&options, state, &mut transaction)
457            .await?;
458
459        sqlx::query(
460            r#"
461            DELETE FROM nest_egg_variables
462            WHERE nest_egg_variables.uuid = $1
463            "#,
464        )
465        .bind(self.uuid)
466        .execute(&mut *transaction)
467        .await?;
468
469        transaction.commit().await?;
470
471        Ok(())
472    }
473}
474
475#[derive(ToSchema, Serialize)]
476#[schema(title = "NestEggVariable")]
477pub struct AdminApiNestEggVariable {
478    pub uuid: uuid::Uuid,
479
480    pub name: compact_str::CompactString,
481    pub description: Option<compact_str::CompactString>,
482    pub order: i16,
483
484    pub env_variable: compact_str::CompactString,
485    pub default_value: Option<String>,
486    pub user_viewable: bool,
487    pub user_editable: bool,
488    pub is_secret: bool,
489    pub rules: Vec<compact_str::CompactString>,
490
491    pub created: chrono::DateTime<chrono::Utc>,
492}