Skip to main content

shared/models/
node_database_host.rs

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}