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 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}