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