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 let offset = (page - 1) * per_page;
125
126 let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
127 r#"
128 SELECT {}, COUNT(*) OVER() AS total_count
129 FROM node_database_hosts
130 JOIN database_hosts ON node_database_hosts.database_host_uuid = database_hosts.uuid
131 WHERE node_database_hosts.node_uuid = $1 AND ($2 IS NULL OR database_hosts.name ILIKE '%' || $2 || '%')
132 ORDER BY node_database_hosts.created
133 LIMIT $3 OFFSET $4
134 "#,
135 Self::columns_sql(None)
136 )))
137 .bind(node_uuid)
138 .bind(search)
139 .bind(per_page)
140 .bind(offset)
141 .fetch_all(database.read())
142 .await?;
143
144 Ok(super::Pagination {
145 total: rows
146 .first()
147 .map_or(Ok(0), |row| row.try_get("total_count"))?,
148 per_page,
149 page,
150 data: rows
151 .into_iter()
152 .map(|row| Self::map(None, &row))
153 .try_collect_vec()?,
154 })
155 }
156}
157
158#[async_trait::async_trait]
159impl IntoAdminApiObject for NodeDatabaseHost {
160 type AdminApiObject = AdminApiNodeDatabaseHost;
161 type ExtraArgs<'a> = ();
162
163 async fn into_admin_api_object<'a>(
164 self,
165 state: &crate::State,
166 _args: Self::ExtraArgs<'a>,
167 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
168 let api_object = AdminApiNodeDatabaseHost::init_hooks(&self, state).await?;
169
170 let api_object = finish_extendible!(
171 AdminApiNodeDatabaseHost {
172 database_host: self.database_host.into_admin_api_object(state, ()).await?,
173 created: self.created.and_utc(),
174 },
175 api_object,
176 state
177 )?;
178
179 Ok(api_object)
180 }
181}
182
183#[derive(ToSchema, Deserialize, Validate)]
184pub struct CreateNodeDatabaseHostOptions {
185 #[garde(skip)]
186 pub node_uuid: uuid::Uuid,
187 #[garde(skip)]
188 pub database_host_uuid: uuid::Uuid,
189}
190
191#[async_trait::async_trait]
192impl CreatableModel for NodeDatabaseHost {
193 type CreateOptions<'a> = CreateNodeDatabaseHostOptions;
194 type CreateResult = Self;
195
196 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
197 static CREATE_LISTENERS: LazyLock<CreateListenerList<NodeDatabaseHost>> =
198 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
199
200 &CREATE_LISTENERS
201 }
202
203 async fn create_with_transaction(
204 state: &crate::State,
205 mut options: Self::CreateOptions<'_>,
206 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
207 ) -> Result<Self, crate::database::DatabaseError> {
208 options.validate()?;
209
210 super::database_host::DatabaseHost::by_uuid_optional_cached(
211 &state.database,
212 options.database_host_uuid,
213 )
214 .await?
215 .ok_or(crate::database::InvalidRelationError("database_host"))?;
216
217 let mut query_builder = InsertQueryBuilder::new("node_database_hosts");
218
219 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
220
221 query_builder
222 .set("node_uuid", options.node_uuid)
223 .set("database_host_uuid", options.database_host_uuid);
224
225 query_builder.execute(&mut **transaction).await?;
226
227 let mut result = match Self::by_node_uuid_database_host_uuid_with_transaction(
228 transaction,
229 options.node_uuid,
230 options.database_host_uuid,
231 )
232 .await?
233 {
234 Some(node_database_host) => node_database_host,
235 None => return Err(sqlx::Error::RowNotFound.into()),
236 };
237
238 Self::run_after_create_handlers(&mut result, &options, state, transaction).await?;
239
240 Ok(result)
241 }
242}
243
244#[async_trait::async_trait]
245impl DeletableModel for NodeDatabaseHost {
246 type DeleteOptions = ();
247
248 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
249 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<NodeDatabaseHost>> =
250 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
251
252 &DELETE_LISTENERS
253 }
254
255 async fn delete_with_transaction(
256 &self,
257 state: &crate::State,
258 options: Self::DeleteOptions,
259 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
260 ) -> Result<(), anyhow::Error> {
261 self.run_delete_handlers(&options, state, transaction)
262 .await?;
263
264 sqlx::query(
265 r#"
266 DELETE FROM node_database_hosts
267 WHERE node_database_hosts.node_uuid = $1 AND node_database_hosts.database_host_uuid = $2
268 "#,
269 )
270 .bind(self.node.uuid)
271 .bind(self.database_host.uuid)
272 .execute(&mut **transaction)
273 .await?;
274
275 self.run_after_delete_handlers(&options, state, transaction)
276 .await?;
277
278 Ok(())
279 }
280}
281
282#[schema_extension_derive::extendible]
283#[init_args(NodeDatabaseHost, crate::State)]
284#[hook_args(crate::State)]
285#[derive(ToSchema, Serialize)]
286#[schema(title = "NodeDatabaseHost")]
287pub struct AdminApiNodeDatabaseHost {
288 pub database_host: super::database_host::AdminApiDatabaseHost,
289
290 pub created: chrono::DateTime<chrono::Utc>,
291}