Skip to main content

shared/models/
server_variable.rs

1use crate::prelude::*;
2use serde::{Deserialize, Serialize};
3use sqlx::{Row, postgres::PgRow};
4use std::{
5    collections::BTreeMap,
6    future::Future,
7    pin::Pin,
8    sync::{Arc, LazyLock},
9};
10
11#[derive(Serialize, Deserialize)]
12pub struct ServerVariable {
13    pub variable: super::nest_egg_variable::NestEggVariable,
14
15    pub value: String,
16
17    pub created: chrono::NaiveDateTime,
18
19    extension_data: super::ModelExtensionData,
20}
21
22impl BaseModel for ServerVariable {
23    const NAME: &'static str = "server_variable";
24
25    fn get_extension_list() -> &'static super::ModelExtensionList {
26        static EXTENSIONS: LazyLock<super::ModelExtensionList> =
27            LazyLock::new(|| parking_lot::RwLock::new(Vec::new()));
28
29        &EXTENSIONS
30    }
31
32    fn get_extension_data(&self) -> &super::ModelExtensionData {
33        &self.extension_data
34    }
35
36    #[inline]
37    fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
38        let prefix = prefix.unwrap_or_default();
39
40        let mut columns = BTreeMap::from([
41            (
42                "server_variables.value",
43                compact_str::format_compact!("{prefix}value"),
44            ),
45            (
46                "server_variables.created",
47                compact_str::format_compact!("{prefix}created"),
48            ),
49        ]);
50
51        columns.extend(super::nest_egg_variable::NestEggVariable::base_columns(
52            Some("variable_"),
53        ));
54
55        columns
56    }
57
58    #[inline]
59    fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
60        let prefix = prefix.unwrap_or_default();
61
62        let variable = super::nest_egg_variable::NestEggVariable::map(Some("variable_"), row)?;
63        let value = row
64            .try_get(compact_str::format_compact!("{prefix}value").as_str())
65            .unwrap_or_else(|_| {
66                variable
67                    .default_value
68                    .clone()
69                    .unwrap_or_else(|| "".to_string())
70            });
71
72        Ok(Self {
73            variable,
74            value,
75            created: row
76                .try_get(compact_str::format_compact!("{prefix}created").as_str())
77                .unwrap_or_else(|_| chrono::Utc::now().naive_utc()),
78            extension_data: Self::map_extensions(prefix, row)?,
79        })
80    }
81}
82
83type RulesHandlerResult<'a> =
84    Pin<Box<dyn Future<Output = Result<(), crate::database::DatabaseError>> + Send + 'a>>;
85type RulesHandler = dyn for<'a> Fn(
86        &'a crate::models::server::Server,
87        &'a str,
88        &'a mut Vec<compact_str::CompactString>,
89    ) -> RulesHandlerResult<'a>
90    + Send
91    + Sync;
92type RulesHandlerList = Arc<ModelHandlerList<Arc<RulesHandler>, Arc<RulesHandler>>>;
93
94impl ServerVariable {
95    pub async fn create(
96        database: &crate::database::Database,
97        server_uuid: uuid::Uuid,
98        variable_uuid: uuid::Uuid,
99        value: &str,
100    ) -> Result<(), crate::database::DatabaseError> {
101        sqlx::query(
102            r#"
103            INSERT INTO server_variables (server_uuid, variable_uuid, value)
104            VALUES ($1, $2, $3)
105            ON CONFLICT (server_uuid, variable_uuid) DO UPDATE SET value = EXCLUDED.value
106            "#,
107        )
108        .bind(server_uuid)
109        .bind(variable_uuid)
110        .bind(value)
111        .execute(database.write())
112        .await?;
113
114        Ok(())
115    }
116
117    pub async fn create_with_transaction(
118        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
119        server_uuid: uuid::Uuid,
120        variable_uuid: uuid::Uuid,
121        value: &str,
122    ) -> Result<(), crate::database::DatabaseError> {
123        sqlx::query(
124            r#"
125            INSERT INTO server_variables (server_uuid, variable_uuid, value)
126            VALUES ($1, $2, $3)
127            ON CONFLICT (server_uuid, variable_uuid) DO UPDATE SET value = EXCLUDED.value
128            "#,
129        )
130        .bind(server_uuid)
131        .bind(variable_uuid)
132        .bind(value)
133        .execute(&mut **transaction)
134        .await?;
135
136        Ok(())
137    }
138
139    // Lets extensions modify the rules a variable is shown and validated with
140    pub fn register_rules_handler<
141        F: for<'a> Fn(
142                &'a crate::models::server::Server,
143                &'a str,
144                &'a mut Vec<compact_str::CompactString>,
145            ) -> Pin<
146                Box<dyn Future<Output = Result<(), crate::database::DatabaseError>> + Send + 'a>,
147            > + Send
148            + Sync
149            + 'static,
150    >(
151        priority: ListenerPriority,
152        callback: F,
153    ) {
154        let erased = Arc::new(callback) as Arc<RulesHandler>;
155
156        Self::get_rules_handlers().register_handler(priority, erased);
157    }
158
159    fn get_rules_handlers() -> &'static LazyLock<RulesHandlerList> {
160        static HANDLERS: LazyLock<RulesHandlerList> = LazyLock::new(Default::default);
161
162        &HANDLERS
163    }
164
165    pub async fn run_rules_handlers(
166        server: &crate::models::server::Server,
167        env_variable: &str,
168        rules: &mut Vec<compact_str::CompactString>,
169    ) -> Result<(), crate::database::DatabaseError> {
170        let callbacks = Self::get_rules_handlers()
171            .before_handlers
172            .read()
173            .iter()
174            .map(|l| l.callback.clone())
175            .collect::<Vec<_>>();
176
177        for callback in callbacks.iter() {
178            (*callback)(server, env_variable, rules).await?;
179        }
180
181        Ok(())
182    }
183
184    pub async fn all_by_server_uuid_egg_uuid(
185        database: &crate::database::Database,
186        server_uuid: uuid::Uuid,
187        egg_uuid: uuid::Uuid,
188    ) -> Result<Vec<Self>, crate::database::DatabaseError> {
189        let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
190            r#"
191            SELECT {}
192            FROM nest_egg_variables
193            LEFT JOIN server_variables ON server_variables.variable_uuid = nest_egg_variables.uuid AND server_variables.server_uuid = $1
194            WHERE nest_egg_variables.egg_uuid = $2
195            ORDER BY nest_egg_variables.order_, nest_egg_variables.created
196            "#,
197            Self::columns_sql(None)
198        )))
199        .bind(server_uuid)
200        .bind(egg_uuid)
201        .fetch_all(database.read())
202        .await?;
203
204        rows.into_iter()
205            .map(|row| Self::map(None, &row))
206            .try_collect_vec()
207    }
208}
209
210#[async_trait::async_trait]
211impl IntoApiObject for ServerVariable {
212    type ApiObject = ApiServerVariable;
213    type ExtraArgs<'a> = ();
214
215    async fn into_api_object<'a>(
216        self,
217        state: &crate::State,
218        _args: Self::ExtraArgs<'a>,
219    ) -> Result<Self::ApiObject, crate::database::DatabaseError> {
220        let api_object = ApiServerVariable::init_hooks(&self, state).await?;
221
222        let api_object = finish_extendible!(
223            ApiServerVariable {
224                name: self.variable.name,
225                name_translations: self.variable.name_translations,
226                description: self.variable.description,
227                description_translations: self.variable.description_translations,
228                env_variable: self.variable.env_variable,
229                default_value: self.variable.default_value,
230                value: self.value,
231                is_editable: self.variable.user_editable,
232                is_secret: self.variable.secret,
233                rules: self.variable.rules,
234                created: self.created.and_utc(),
235            },
236            api_object,
237            state
238        )?;
239
240        Ok(api_object)
241    }
242}
243
244#[schema_extension_derive::extendible]
245#[init_args(ServerVariable, crate::State)]
246#[hook_args(crate::State)]
247#[derive(ToSchema, Serialize)]
248#[schema(title = "ServerVariable")]
249pub struct ApiServerVariable {
250    pub name: compact_str::CompactString,
251    pub name_translations: BTreeMap<compact_str::CompactString, compact_str::CompactString>,
252    pub description: Option<compact_str::CompactString>,
253    pub description_translations: BTreeMap<compact_str::CompactString, compact_str::CompactString>,
254
255    pub env_variable: compact_str::CompactString,
256    pub default_value: Option<String>,
257    pub value: String,
258    pub is_editable: bool,
259    pub is_secret: bool,
260    pub rules: Vec<compact_str::CompactString>,
261
262    pub created: chrono::DateTime<chrono::Utc>,
263}