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
14pub fn validate_config_allocations(
15 config_allocations: &EggConfigAllocations,
16 _context: &(),
17) -> Result<(), garde::Error> {
18 if !config_allocations.user_self_assign.is_valid() {
19 return Err(garde::Error::new(
20 "port ranges must be 1024-65535 and start_port < end_port",
21 ));
22 }
23
24 Ok(())
25}
26
27#[derive(ToSchema, Serialize, Deserialize, Clone, Copy)]
28pub struct EggConfigAllocationsUserSelfAssign {
29 pub enabled: bool,
30 pub require_primary_allocation: bool,
31
32 pub start_port: u16,
33 pub end_port: u16,
34}
35
36impl Default for EggConfigAllocationsUserSelfAssign {
37 fn default() -> Self {
38 Self {
39 enabled: false,
40 require_primary_allocation: true,
41 start_port: 49152,
42 end_port: 65535,
43 }
44 }
45}
46
47impl EggConfigAllocationsUserSelfAssign {
48 #[inline]
49 pub fn is_valid(&self) -> bool {
50 self.start_port < self.end_port && self.start_port >= 1024
51 }
52}
53
54#[derive(ToSchema, Validate, Serialize, Deserialize, Clone)]
55#[serde(tag = "type", rename_all = "snake_case")]
56pub enum EggConfigAllocationDeploymentAdditionalAllocationMode {
57 Random,
58 Range {
59 #[garde(range(min = 1024, max = 65535))]
60 start_port: u16,
61 #[garde(range(min = 1024, max = 65535))]
62 end_port: u16,
63 },
64 AddPrimary {
65 #[garde(skip)]
66 value: u16,
67 },
68 SubtractPrimary {
69 #[garde(skip)]
70 value: u16,
71 },
72 MultiplyPrimary {
73 #[garde(skip)]
74 value: f64,
75 },
76 DividePrimary {
77 #[garde(skip)]
78 value: f64,
79 },
80}
81
82#[derive(ToSchema, Validate, Serialize, Deserialize, Clone)]
83pub struct EggConfigAllocationDeploymentAdditionalAllocation {
84 #[schema(inline)]
85 #[garde(dive)]
86 pub mode: EggConfigAllocationDeploymentAdditionalAllocationMode,
87 #[garde(length(chars, min = 1, max = 255))]
88 pub assign_to_variable: Option<compact_str::CompactString>,
89}
90
91#[derive(ToSchema, Validate, Serialize, Deserialize, Clone)]
92pub struct EggConfigAllocationDeploymentPrimaryAllocation {
93 #[garde(range(min = 1024, max = 65535))]
94 pub start_port: u16,
95 #[garde(range(min = 1024, max = 65535))]
96 pub end_port: u16,
97
98 #[garde(length(chars, min = 1, max = 255))]
99 pub assign_to_variable: Option<compact_str::CompactString>,
100}
101
102#[derive(ToSchema, Validate, Serialize, Deserialize, Default, Clone)]
103pub struct EggConfigAllocationsDeployment {
104 #[garde(skip)]
105 pub dedicated: bool,
106
107 #[schema(inline)]
108 #[garde(dive)]
109 pub primary: Option<EggConfigAllocationDeploymentPrimaryAllocation>,
110 #[schema(inline)]
111 #[garde(dive)]
112 pub additional: Vec<EggConfigAllocationDeploymentAdditionalAllocation>,
113}
114
115#[derive(ToSchema, Serialize, Deserialize, Default, Clone)]
116pub struct EggConfigAllocations {
117 #[serde(default)]
118 pub user_self_assign: EggConfigAllocationsUserSelfAssign,
119 #[serde(default)]
120 pub deployment: EggConfigAllocationsDeployment,
121}
122
123#[derive(ToSchema, Validate, Serialize, Deserialize, Default, Clone)]
124pub struct EggConfigStartup {
125 #[garde(skip)]
126 pub allow_custom_startup_command: bool,
127}
128
129#[derive(ToSchema, Validate, Serialize, Deserialize, Default, Clone)]
130pub struct EggConfigRoutes {
131 #[garde(length(max = 100))]
132 #[schema(max_length = 100)]
133 pub order: Vec<crate::settings::RouteOrderItem>,
134}
135
136#[derive(Serialize, Deserialize, Clone)]
137pub struct EggConfiguration {
138 pub uuid: uuid::Uuid,
139
140 pub name: compact_str::CompactString,
141 pub description: Option<compact_str::CompactString>,
142 pub order: i16,
143
144 pub eggs: Vec<uuid::Uuid>,
145
146 pub config_allocations: Option<EggConfigAllocations>,
147 pub config_startup: Option<EggConfigStartup>,
148 pub config_routes: Option<EggConfigRoutes>,
149
150 pub created: chrono::NaiveDateTime,
151
152 extension_data: super::ModelExtensionData,
153}
154
155impl BaseModel for EggConfiguration {
156 const NAME: &'static str = "egg_configuration";
157
158 fn get_extension_list() -> &'static super::ModelExtensionList {
159 static EXTENSIONS: LazyLock<super::ModelExtensionList> =
160 LazyLock::new(|| parking_lot::RwLock::new(Vec::new()));
161
162 &EXTENSIONS
163 }
164
165 fn get_extension_data(&self) -> &super::ModelExtensionData {
166 &self.extension_data
167 }
168
169 #[inline]
170 fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
171 let prefix = prefix.unwrap_or_default();
172
173 BTreeMap::from([
174 (
175 "egg_configurations.uuid",
176 compact_str::format_compact!("{prefix}uuid"),
177 ),
178 (
179 "egg_configurations.name",
180 compact_str::format_compact!("{prefix}name"),
181 ),
182 (
183 "egg_configurations.description",
184 compact_str::format_compact!("{prefix}description"),
185 ),
186 (
187 "egg_configurations.order_",
188 compact_str::format_compact!("{prefix}order_"),
189 ),
190 (
191 "egg_configurations.eggs",
192 compact_str::format_compact!("{prefix}eggs"),
193 ),
194 (
195 "egg_configurations.config_allocations",
196 compact_str::format_compact!("{prefix}config_allocations"),
197 ),
198 (
199 "egg_configurations.config_startup",
200 compact_str::format_compact!("{prefix}config_startup"),
201 ),
202 (
203 "egg_configurations.config_routes",
204 compact_str::format_compact!("{prefix}config_routes"),
205 ),
206 (
207 "egg_configurations.created",
208 compact_str::format_compact!("{prefix}created"),
209 ),
210 ])
211 }
212
213 #[inline]
214 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
215 let prefix = prefix.unwrap_or_default();
216
217 Ok(Self {
218 uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
219 name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
220 description: row
221 .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
222 order: row.try_get(compact_str::format_compact!("{prefix}order_").as_str())?,
223 eggs: row.try_get(compact_str::format_compact!("{prefix}eggs").as_str())?,
224 config_allocations: row
225 .try_get::<Option<serde_json::Value>, _>(
226 compact_str::format_compact!("{prefix}config_allocations").as_str(),
227 )?
228 .and_then(|v| serde_json::from_value(v).ok()),
229 config_startup: row
230 .try_get::<Option<serde_json::Value>, _>(
231 compact_str::format_compact!("{prefix}config_startup").as_str(),
232 )?
233 .and_then(|v| serde_json::from_value(v).ok()),
234 config_routes: row
235 .try_get::<Option<serde_json::Value>, _>(
236 compact_str::format_compact!("{prefix}config_routes").as_str(),
237 )?
238 .and_then(|v| serde_json::from_value(v).ok()),
239 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
240 extension_data: Self::map_extensions(prefix, row)?,
241 })
242 }
243}
244
245impl EggConfiguration {
246 pub async fn all_with_pagination(
247 database: &crate::database::Database,
248 page: i64,
249 per_page: i64,
250 search: Option<&str>,
251 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
252 let offset = (page - 1) * per_page;
253
254 let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
255 r#"
256 SELECT {}, COUNT(*) OVER() AS total_count
257 FROM egg_configurations
258 WHERE ($1 IS NULL OR egg_configurations.name ILIKE '%' || $1 || '%')
259 ORDER BY egg_configurations.order_, egg_configurations.created
260 LIMIT $2 OFFSET $3
261 "#,
262 Self::columns_sql(None)
263 )))
264 .bind(search)
265 .bind(per_page)
266 .bind(offset)
267 .fetch_all(database.read())
268 .await?;
269
270 Ok(super::Pagination {
271 total: rows
272 .first()
273 .map_or(Ok(0), |row| row.try_get("total_count"))?,
274 per_page,
275 page,
276 data: rows
277 .into_iter()
278 .map(|row| Self::map(None, &row))
279 .try_collect_vec()?,
280 })
281 }
282
283 pub async fn merged_by_egg_uuid(
284 database: &crate::database::Database,
285 egg_uuid: uuid::Uuid,
286 ) -> Result<MergedEggConfiguration, crate::database::DatabaseError> {
287 let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
288 r#"
289 SELECT {}
290 FROM egg_configurations
291 WHERE $1 = ANY(egg_configurations.eggs)
292 ORDER BY egg_configurations.order_, egg_configurations.created
293 "#,
294 Self::columns_sql(None)
295 )))
296 .bind(egg_uuid)
297 .fetch_all(database.read())
298 .await?;
299
300 let rows = rows
301 .into_iter()
302 .map(|row| Self::map(None, &row))
303 .try_collect_vec()?;
304
305 let mut base = MergedEggConfiguration {
306 config_allocations: None,
307 config_startup: None,
308 config_routes: None,
309 };
310
311 for row in rows {
312 if row.config_allocations.is_some() {
313 base.config_allocations = row.config_allocations;
314 }
315 if row.config_startup.is_some() {
316 base.config_startup = row.config_startup;
317 }
318 if row.config_routes.is_some() {
319 base.config_routes = row.config_routes;
320 }
321 }
322
323 Ok(base)
324 }
325
326 pub async fn cleanup_uuid_arrays(
327 database: &crate::database::Database,
328 ) -> Result<u64, crate::database::DatabaseError> {
329 let result = sqlx::query(
330 "UPDATE egg_configurations
331 SET eggs = COALESCE(
332 (SELECT array_agg(u) FROM unnest(eggs) AS u
333 WHERE EXISTS (SELECT 1 FROM nest_eggs WHERE uuid = u)),
334 '{}'::uuid[]
335 )
336 WHERE EXISTS (
337 SELECT 1 FROM unnest(eggs) AS u
338 WHERE NOT EXISTS (SELECT 1 FROM nest_eggs WHERE uuid = u)
339 )",
340 )
341 .execute(database.write())
342 .await?;
343
344 Ok(result.rows_affected())
345 }
346}
347
348#[async_trait::async_trait]
349impl IntoAdminApiObject for EggConfiguration {
350 type AdminApiObject = AdminApiEggConfiguration;
351 type ExtraArgs<'a> = ();
352
353 async fn into_admin_api_object<'a>(
354 self,
355 state: &crate::State,
356 _args: Self::ExtraArgs<'a>,
357 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
358 let api_object = AdminApiEggConfiguration::init_hooks(&self, state).await?;
359
360 let api_object = finish_extendible!(
361 AdminApiEggConfiguration {
362 uuid: self.uuid,
363 name: self.name,
364 description: self.description,
365 order: self.order,
366 eggs: self.eggs,
367 config_allocations: self.config_allocations,
368 config_startup: self.config_startup,
369 config_routes: self.config_routes,
370 created: self.created.and_utc(),
371 },
372 api_object,
373 state
374 )?;
375
376 Ok(api_object)
377 }
378}
379
380#[async_trait::async_trait]
381impl ByUuid for EggConfiguration {
382 async fn by_uuid(
383 database: &crate::database::Database,
384 uuid: uuid::Uuid,
385 ) -> Result<Self, crate::database::DatabaseError> {
386 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
387 r#"
388 SELECT {}
389 FROM egg_configurations
390 WHERE egg_configurations.uuid = $1
391 "#,
392 Self::columns_sql(None)
393 )))
394 .bind(uuid)
395 .fetch_one(database.read())
396 .await?;
397
398 Self::map(None, &row)
399 }
400
401 async fn by_uuid_with_transaction(
402 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
403 uuid: uuid::Uuid,
404 ) -> Result<Self, crate::database::DatabaseError> {
405 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
406 r#"
407 SELECT {}
408 FROM egg_configurations
409 WHERE egg_configurations.uuid = $1
410 "#,
411 Self::columns_sql(None)
412 )))
413 .bind(uuid)
414 .fetch_one(&mut **transaction)
415 .await?;
416
417 Self::map(None, &row)
418 }
419}
420
421#[derive(ToSchema, Deserialize, Validate)]
422pub struct CreateEggConfigurationOptions {
423 #[garde(length(chars, min = 1, max = 255))]
424 #[schema(min_length = 1, max_length = 255)]
425 pub name: compact_str::CompactString,
426 #[garde(length(chars, min = 1, max = 1024))]
427 #[schema(min_length = 1, max_length = 1024)]
428 pub description: Option<compact_str::CompactString>,
429 #[garde(skip)]
430 pub order: i16,
431 #[garde(length(max = 100))]
432 #[schema(max_length = 100)]
433 pub eggs: Vec<uuid::Uuid>,
434 #[garde(inner(custom(validate_config_allocations)))]
435 #[schema(inline)]
436 pub config_allocations: Option<EggConfigAllocations>,
437 #[garde(dive)]
438 #[schema(inline)]
439 pub config_startup: Option<EggConfigStartup>,
440 #[garde(dive)]
441 #[schema(inline)]
442 pub config_routes: Option<EggConfigRoutes>,
443}
444
445#[async_trait::async_trait]
446impl CreatableModel for EggConfiguration {
447 type CreateOptions<'a> = CreateEggConfigurationOptions;
448 type CreateResult = Self;
449
450 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
451 static CREATE_LISTENERS: LazyLock<CreateListenerList<EggConfiguration>> =
452 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
453
454 &CREATE_LISTENERS
455 }
456
457 async fn create_with_transaction(
458 state: &crate::State,
459 mut options: Self::CreateOptions<'_>,
460 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
461 ) -> Result<Self, crate::database::DatabaseError> {
462 options.validate()?;
463
464 let mut query_builder = InsertQueryBuilder::new("egg_configurations");
465
466 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
467
468 query_builder
469 .set("name", &options.name)
470 .set("description", &options.description)
471 .set("order_", options.order)
472 .set("eggs", &options.eggs)
473 .set(
474 "config_allocations",
475 options
476 .config_allocations
477 .as_ref()
478 .map(serde_json::to_value)
479 .transpose()?,
480 )
481 .set(
482 "config_startup",
483 options
484 .config_startup
485 .as_ref()
486 .map(serde_json::to_value)
487 .transpose()?,
488 )
489 .set(
490 "config_routes",
491 options
492 .config_routes
493 .as_ref()
494 .map(serde_json::to_value)
495 .transpose()?,
496 );
497
498 let row = query_builder
499 .returning(&Self::columns_sql(None))
500 .fetch_one(&mut **transaction)
501 .await?;
502 let mut egg_configuration = Self::map(None, &row)?;
503
504 Self::run_after_create_handlers(&mut egg_configuration, &options, state, transaction)
505 .await?;
506
507 Ok(egg_configuration)
508 }
509}
510
511#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
512pub struct UpdateEggConfigurationOptions {
513 #[garde(length(chars, min = 1, max = 255))]
514 #[schema(min_length = 1, max_length = 255)]
515 pub name: Option<compact_str::CompactString>,
516 #[garde(length(chars, min = 1, max = 1024))]
517 #[schema(min_length = 1, max_length = 1024)]
518 #[serde(
519 default,
520 skip_serializing_if = "Option::is_none",
521 with = "::serde_with::rust::double_option"
522 )]
523 pub description: Option<Option<compact_str::CompactString>>,
524 #[garde(skip)]
525 pub order: Option<i16>,
526 #[garde(length(max = 100))]
527 #[schema(max_length = 100)]
528 pub eggs: Option<Vec<uuid::Uuid>>,
529
530 #[garde(inner(inner(custom(validate_config_allocations))))]
531 #[schema(inline)]
532 #[serde(
533 default,
534 skip_serializing_if = "Option::is_none",
535 with = "::serde_with::rust::double_option"
536 )]
537 pub config_allocations: Option<Option<EggConfigAllocations>>,
538 #[garde(dive)]
539 #[schema(inline)]
540 #[serde(
541 default,
542 skip_serializing_if = "Option::is_none",
543 with = "::serde_with::rust::double_option"
544 )]
545 pub config_startup: Option<Option<EggConfigStartup>>,
546 #[garde(dive)]
547 #[schema(inline)]
548 #[serde(
549 default,
550 skip_serializing_if = "Option::is_none",
551 with = "::serde_with::rust::double_option"
552 )]
553 pub config_routes: Option<Option<EggConfigRoutes>>,
554}
555
556#[async_trait::async_trait]
557impl UpdatableModel for EggConfiguration {
558 type UpdateOptions = UpdateEggConfigurationOptions;
559
560 fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
561 static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<EggConfiguration>> =
562 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
563
564 &UPDATE_LISTENERS
565 }
566
567 async fn update_with_transaction(
568 &mut self,
569 state: &crate::State,
570 mut options: Self::UpdateOptions,
571 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
572 ) -> Result<(), crate::database::DatabaseError> {
573 options.validate()?;
574
575 let mut query_builder = UpdateQueryBuilder::new("egg_configurations");
576
577 self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
578 .await?;
579
580 query_builder
581 .set("name", options.name.as_ref())
582 .set(
583 "description",
584 options.description.as_ref().map(|d| d.as_ref()),
585 )
586 .set("order_", options.order)
587 .set("eggs", options.eggs.as_ref())
588 .set(
589 "config_allocations",
590 options
591 .config_allocations
592 .as_ref()
593 .map(|c| c.as_ref().map(serde_json::to_value).transpose())
594 .transpose()?,
595 )
596 .set(
597 "config_startup",
598 options
599 .config_startup
600 .as_ref()
601 .map(|c| c.as_ref().map(serde_json::to_value).transpose())
602 .transpose()?,
603 )
604 .set(
605 "config_routes",
606 options
607 .config_routes
608 .as_ref()
609 .map(|c| c.as_ref().map(serde_json::to_value).transpose())
610 .transpose()?,
611 )
612 .where_eq("uuid", self.uuid);
613
614 query_builder.execute(&mut **transaction).await?;
615
616 if let Some(name) = options.name {
617 self.name = name;
618 }
619 if let Some(description) = options.description {
620 self.description = description;
621 }
622 if let Some(order) = options.order {
623 self.order = order;
624 }
625 if let Some(eggs) = options.eggs {
626 self.eggs = eggs;
627 }
628 if let Some(config_allocations) = options.config_allocations {
629 self.config_allocations = config_allocations;
630 }
631 if let Some(config_startup) = options.config_startup {
632 self.config_startup = config_startup;
633 }
634 if let Some(config_routes) = options.config_routes {
635 self.config_routes = config_routes;
636 }
637
638 self.run_after_update_handlers(state, transaction).await?;
639
640 Ok(())
641 }
642}
643
644#[async_trait::async_trait]
645impl DeletableModel for EggConfiguration {
646 type DeleteOptions = ();
647
648 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
649 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<EggConfiguration>> =
650 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
651
652 &DELETE_LISTENERS
653 }
654
655 async fn delete_with_transaction(
656 &self,
657 state: &crate::State,
658 options: Self::DeleteOptions,
659 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
660 ) -> Result<(), anyhow::Error> {
661 self.run_delete_handlers(&options, state, transaction)
662 .await?;
663
664 sqlx::query(
665 r#"
666 DELETE FROM egg_configurations
667 WHERE egg_configurations.uuid = $1
668 "#,
669 )
670 .bind(self.uuid)
671 .execute(&mut **transaction)
672 .await?;
673
674 self.run_after_delete_handlers(&options, state, transaction)
675 .await?;
676
677 Ok(())
678 }
679}
680
681#[derive(Validate)]
682pub struct DuplicateEggConfigurationOptions {
683 #[garde(length(chars, min = 1, max = 255))]
684 pub name: compact_str::CompactString,
685}
686
687#[async_trait::async_trait]
688impl DuplicableModel for EggConfiguration {
689 type DuplicateOptions<'a> = DuplicateEggConfigurationOptions;
690
691 fn get_duplicate_handlers() -> &'static LazyLock<DuplicateHandlerList<Self>> {
692 static DUPLICATE_LISTENERS: LazyLock<DuplicateHandlerList<EggConfiguration>> =
693 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
694
695 &DUPLICATE_LISTENERS
696 }
697
698 async fn duplicate_with_transaction(
699 &self,
700 state: &crate::State,
701 options: Self::DuplicateOptions<'_>,
702 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
703 ) -> Result<Self, crate::database::DatabaseError> {
704 options.validate()?;
705
706 self.run_duplicate_handlers(&options, state, transaction)
707 .await?;
708
709 let mut query_builder = InsertQueryBuilder::new("egg_configurations");
710
711 query_builder
712 .set("name", &options.name)
713 .set("description", &self.description)
714 .set("order_", self.order)
715 .set("eggs", &self.eggs)
716 .set(
717 "config_allocations",
718 self.config_allocations
719 .as_ref()
720 .map(serde_json::to_value)
721 .transpose()?,
722 )
723 .set(
724 "config_startup",
725 self.config_startup
726 .as_ref()
727 .map(serde_json::to_value)
728 .transpose()?,
729 )
730 .set(
731 "config_routes",
732 self.config_routes
733 .as_ref()
734 .map(serde_json::to_value)
735 .transpose()?,
736 );
737
738 let row = query_builder
739 .returning(&Self::columns_sql(None))
740 .fetch_one(&mut **transaction)
741 .await?;
742 let mut egg_configuration = Self::map(None, &row)?;
743
744 self.run_after_duplicate_handlers(&mut egg_configuration, &options, state, transaction)
745 .await?;
746
747 Ok(egg_configuration)
748 }
749}
750
751#[schema_extension_derive::extendible]
752#[init_args(EggConfiguration, crate::State)]
753#[hook_args(crate::State)]
754#[derive(ToSchema, Serialize)]
755#[schema(title = "AdminEggConfiguration")]
756pub struct AdminApiEggConfiguration {
757 pub uuid: uuid::Uuid,
758
759 pub name: compact_str::CompactString,
760 pub description: Option<compact_str::CompactString>,
761 pub order: i16,
762
763 pub eggs: Vec<uuid::Uuid>,
764
765 #[schema(inline)]
766 pub config_allocations: Option<EggConfigAllocations>,
767 #[schema(inline)]
768 pub config_startup: Option<EggConfigStartup>,
769 #[schema(inline)]
770 pub config_routes: Option<EggConfigRoutes>,
771
772 pub created: chrono::DateTime<chrono::Utc>,
773}
774
775#[derive(Deserialize, Serialize)]
776pub struct MergedEggConfiguration {
777 pub config_allocations: Option<EggConfigAllocations>,
778 pub config_startup: Option<EggConfigStartup>,
779 pub config_routes: Option<EggConfigRoutes>,
780}
781
782impl MergedEggConfiguration {}
783
784#[async_trait::async_trait]
785impl IntoApiObject for MergedEggConfiguration {
786 type ApiObject = ApiEggConfiguration;
787 type ExtraArgs<'a> = ();
788
789 async fn into_api_object<'a>(
790 self,
791 state: &crate::State,
792 _args: Self::ExtraArgs<'a>,
793 ) -> Result<Self::ApiObject, crate::database::DatabaseError> {
794 let api_object = ApiEggConfiguration::init_hooks(&self, state).await?;
795
796 let api_object = finish_extendible!(
797 ApiEggConfiguration {
798 allocation_self_assign_enabled: self
799 .config_allocations
800 .as_ref()
801 .is_some_and(|c| c.user_self_assign.enabled),
802 allocation_self_assign_require_primary: self
803 .config_allocations
804 .as_ref()
805 .is_some_and(|c| c.user_self_assign.require_primary_allocation),
806 startup_allow_custom_command: self
807 .config_startup
808 .as_ref()
809 .is_some_and(|c| c.allow_custom_startup_command),
810 route_order: self.config_routes.map(|c| c.order),
811 },
812 api_object,
813 state
814 )?;
815
816 Ok(api_object)
817 }
818}
819
820#[schema_extension_derive::extendible]
821#[init_args(MergedEggConfiguration, crate::State)]
822#[hook_args(crate::State)]
823#[derive(ToSchema, Serialize)]
824#[schema(title = "NestEggConfiguration")]
825pub struct ApiEggConfiguration {
826 pub allocation_self_assign_enabled: bool,
827 pub allocation_self_assign_require_primary: bool,
828 pub startup_allow_custom_command: bool,
829 pub route_order: Option<Vec<crate::settings::RouteOrderItem>>,
830}