1use crate::{models::InsertQueryBuilder, prelude::*};
2use garde::Validate;
3use serde::{Deserialize, Serialize};
4use sqlx::{Row, postgres::PgRow};
5use std::{
6 collections::BTreeMap,
7 sync::{Arc, LazyLock},
8};
9use utoipa::ToSchema;
10
11#[derive(Serialize, Deserialize, Clone)]
12pub struct NodeDatabaseHost {
13 pub node: Fetchable<super::node::Node>,
14 pub database_host: super::database_host::DatabaseHost,
15
16 pub created: chrono::NaiveDateTime,
17
18 extension_data: super::ModelExtensionData,
19}
20
21impl BaseModel for NodeDatabaseHost {
22 const NAME: &'static str = "node_database_host";
23
24 fn get_extension_list() -> &'static super::ModelExtensionList {
25 static EXTENSIONS: LazyLock<super::ModelExtensionList> =
26 LazyLock::new(|| parking_lot::RwLock::new(Vec::new()));
27
28 &EXTENSIONS
29 }
30
31 fn get_extension_data(&self) -> &super::ModelExtensionData {
32 &self.extension_data
33 }
34
35 #[inline]
36 fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
37 let prefix = prefix.unwrap_or_default();
38
39 let mut columns = BTreeMap::from([
40 (
41 "node_database_hosts.node_uuid",
42 compact_str::format_compact!("{prefix}node_uuid"),
43 ),
44 (
45 "node_database_hosts.created",
46 compact_str::format_compact!("{prefix}created"),
47 ),
48 ]);
49
50 columns.extend(super::database_host::DatabaseHost::base_columns(Some(
51 "database_host_",
52 )));
53
54 columns
55 }
56
57 #[inline]
58 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
59 let prefix = prefix.unwrap_or_default();
60
61 Ok(Self {
62 node: super::node::Node::get_fetchable(
63 row.try_get(compact_str::format_compact!("{prefix}node_uuid").as_str())?,
64 ),
65 database_host: super::database_host::DatabaseHost::map(Some("database_host_"), row)?,
66 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
67 extension_data: Self::map_extensions(prefix, row)?,
68 })
69 }
70}
71
72impl NodeDatabaseHost {
73 pub async fn by_node_uuid_database_host_uuid(
74 database: &crate::database::Database,
75 node_uuid: uuid::Uuid,
76 database_host_uuid: uuid::Uuid,
77 ) -> Result<Option<Self>, crate::database::DatabaseError> {
78 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
79 r#"
80 SELECT {}
81 FROM node_database_hosts
82 JOIN database_hosts ON node_database_hosts.database_host_uuid = database_hosts.uuid
83 WHERE node_database_hosts.node_uuid = $1 AND node_database_hosts.database_host_uuid = $2
84 "#,
85 Self::columns_sql(None)
86 )))
87 .bind(node_uuid)
88 .bind(database_host_uuid)
89 .fetch_optional(database.read())
90 .await?;
91
92 row.try_map(|row| Self::map(None, &row))
93 }
94
95 pub async fn by_node_uuid_database_host_uuid_with_transaction(
96 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
97 node_uuid: uuid::Uuid,
98 database_host_uuid: uuid::Uuid,
99 ) -> Result<Option<Self>, crate::database::DatabaseError> {
100 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
101 r#"
102 SELECT {}
103 FROM node_database_hosts
104 JOIN database_hosts ON node_database_hosts.database_host_uuid = database_hosts.uuid
105 WHERE node_database_hosts.node_uuid = $1 AND node_database_hosts.database_host_uuid = $2
106 "#,
107 Self::columns_sql(None)
108 )))
109 .bind(node_uuid)
110 .bind(database_host_uuid)
111 .fetch_optional(&mut **transaction)
112 .await?;
113
114 row.try_map(|row| Self::map(None, &row))
115 }
116
117 pub async fn by_node_uuid_with_pagination(
118 database: &crate::database::Database,
119 node_uuid: uuid::Uuid,
120 page: i64,
121 per_page: i64,
122 search: Option<&str>,
123 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
124 super::PaginatedQueryBuilder::new(
125 Self::columns_sql(None),
126 format!(
127 r#"
128 FROM node_database_hosts
129 JOIN database_hosts ON node_database_hosts.database_host_uuid = database_hosts.uuid
130 WHERE node_database_hosts.node_uuid = $1 AND {search}
131 "#,
132 search = super::search_sql(2, &["database_hosts.name"], &["database_hosts.uuid"]),
133 ),
134 "node_database_hosts.created, node_database_hosts.node_uuid, node_database_hosts.database_host_uuid",
135 )
136 .bind(node_uuid)
137 .bind(search)
138 .fetch(database, page, per_page)
139 .await
140 }
141}
142
143#[async_trait::async_trait]
144impl IntoAdminApiObject for NodeDatabaseHost {
145 type AdminApiObject = AdminApiNodeDatabaseHost;
146 type ExtraArgs<'a> = ();
147
148 async fn into_admin_api_object<'a>(
149 self,
150 state: &crate::State,
151 _args: Self::ExtraArgs<'a>,
152 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
153 let api_object = AdminApiNodeDatabaseHost::init_hooks(&self, state).await?;
154
155 let api_object = finish_extendible!(
156 AdminApiNodeDatabaseHost {
157 database_host: self.database_host.into_admin_api_object(state, ()).await?,
158 created: self.created.and_utc(),
159 },
160 api_object,
161 state
162 )?;
163
164 Ok(api_object)
165 }
166}
167
168#[derive(ToSchema, Deserialize, Validate)]
169pub struct CreateNodeDatabaseHostOptions {
170 #[garde(skip)]
171 pub node_uuid: uuid::Uuid,
172 #[garde(skip)]
173 pub database_host_uuid: uuid::Uuid,
174}
175
176#[async_trait::async_trait]
177impl CreatableModel for NodeDatabaseHost {
178 type CreateOptions<'a> = CreateNodeDatabaseHostOptions;
179 type CreateResult = Self;
180
181 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
182 static CREATE_LISTENERS: LazyLock<CreateListenerList<NodeDatabaseHost>> =
183 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
184
185 &CREATE_LISTENERS
186 }
187
188 async fn create_with_transaction(
189 state: &crate::State,
190 mut options: Self::CreateOptions<'_>,
191 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
192 ) -> Result<Self, crate::database::DatabaseError> {
193 options.validate()?;
194
195 super::database_host::DatabaseHost::by_uuid_optional_cached(
196 &state.database,
197 options.database_host_uuid,
198 )
199 .await?
200 .ok_or(crate::database::InvalidRelationError("database_host"))?;
201
202 let mut query_builder = InsertQueryBuilder::new("node_database_hosts");
203
204 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
205
206 query_builder
207 .set("node_uuid", options.node_uuid)
208 .set("database_host_uuid", options.database_host_uuid);
209
210 query_builder.execute(&mut **transaction).await?;
211
212 let mut result = match Self::by_node_uuid_database_host_uuid_with_transaction(
213 transaction,
214 options.node_uuid,
215 options.database_host_uuid,
216 )
217 .await?
218 {
219 Some(node_database_host) => node_database_host,
220 None => return Err(sqlx::Error::RowNotFound.into()),
221 };
222
223 Self::run_after_create_handlers(&mut result, &options, state, transaction).await?;
224
225 Ok(result)
226 }
227}
228
229#[async_trait::async_trait]
230impl DeletableModel for NodeDatabaseHost {
231 type DeleteOptions = ();
232
233 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
234 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<NodeDatabaseHost>> =
235 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
236
237 &DELETE_LISTENERS
238 }
239
240 async fn delete_with_transaction(
241 &self,
242 state: &crate::State,
243 options: Self::DeleteOptions,
244 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
245 ) -> Result<(), anyhow::Error> {
246 self.run_delete_handlers(&options, state, transaction)
247 .await?;
248
249 sqlx::query(
250 r#"
251 DELETE FROM node_database_hosts
252 WHERE node_database_hosts.node_uuid = $1 AND node_database_hosts.database_host_uuid = $2
253 "#,
254 )
255 .bind(self.node.uuid)
256 .bind(self.database_host.uuid)
257 .execute(&mut **transaction)
258 .await?;
259
260 self.run_after_delete_handlers(&options, state, transaction)
261 .await?;
262
263 Ok(())
264 }
265}
266
267#[schema_extension_derive::extendible]
268#[init_args(NodeDatabaseHost, crate::State)]
269#[hook_args(crate::State)]
270#[derive(ToSchema, Serialize)]
271#[schema(title = "NodeDatabaseHost")]
272pub struct AdminApiNodeDatabaseHost {
273 pub database_host: super::database_host::AdminApiDatabaseHost,
274
275 pub created: chrono::DateTime<chrono::Utc>,
276}