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}