Skip to main content

shared/models/
location_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 LocationDatabaseHost {
13    pub location: Fetchable<super::location::Location>,
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 LocationDatabaseHost {
22    const NAME: &'static str = "location_database_host";
23
24    fn get_extension_list() -> &'static super::ModelExtensionList {
25        static EXTENSIONS: LazyLock<super::ModelExtensionList> =
26            LazyLock::new(|| std::sync::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                "location_database_hosts.location_uuid",
42                compact_str::format_compact!("{prefix}location_uuid"),
43            ),
44            (
45                "location_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            location: super::location::Location::get_fetchable(
63                row.try_get(compact_str::format_compact!("{prefix}location_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 LocationDatabaseHost {
73    pub async fn by_location_uuid_database_host_uuid(
74        database: &crate::database::Database,
75        location_uuid: uuid::Uuid,
76        database_host_uuid: uuid::Uuid,
77    ) -> Result<Option<Self>, crate::database::DatabaseError> {
78        let row = sqlx::query(&format!(
79            r#"
80            SELECT {}
81            FROM location_database_hosts
82            JOIN database_hosts ON location_database_hosts.database_host_uuid = database_hosts.uuid
83            WHERE location_database_hosts.location_uuid = $1 AND location_database_hosts.database_host_uuid = $2
84            "#,
85            Self::columns_sql(None)
86        ))
87        .bind(location_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_location_uuid_database_host_uuid_with_transaction(
96        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
97        location_uuid: uuid::Uuid,
98        database_host_uuid: uuid::Uuid,
99    ) -> Result<Option<Self>, crate::database::DatabaseError> {
100        let row = sqlx::query(&format!(
101            r#"
102            SELECT {}
103            FROM location_database_hosts
104            JOIN database_hosts ON location_database_hosts.database_host_uuid = database_hosts.uuid
105            WHERE location_database_hosts.location_uuid = $1 AND location_database_hosts.database_host_uuid = $2
106            "#,
107            Self::columns_sql(None)
108        ))
109        .bind(location_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_location_uuid_with_pagination(
118        database: &crate::database::Database,
119        location_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(&format!(
127            r#"
128            SELECT {}, COUNT(*) OVER() AS total_count
129            FROM location_database_hosts
130            JOIN database_hosts ON location_database_hosts.database_host_uuid = database_hosts.uuid
131            WHERE location_database_hosts.location_uuid = $1 AND ($2 IS NULL OR database_hosts.name ILIKE '%' || $2 || '%')
132            ORDER BY location_database_hosts.created
133            LIMIT $3 OFFSET $4
134            "#,
135            Self::columns_sql(None)
136        ))
137        .bind(location_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    pub async fn all_public_by_location_uuid(
158        database: &crate::database::Database,
159        location_uuid: uuid::Uuid,
160    ) -> Result<Vec<Self>, crate::database::DatabaseError> {
161        let rows = sqlx::query(&format!(
162            r#"
163            SELECT {}
164            FROM location_database_hosts
165            JOIN database_hosts ON location_database_hosts.database_host_uuid = database_hosts.uuid
166            WHERE location_database_hosts.location_uuid = $1 AND database_hosts.deployment_enabled
167            ORDER BY location_database_hosts.created DESC
168            "#,
169            Self::columns_sql(None)
170        ))
171        .bind(location_uuid)
172        .fetch_all(database.read())
173        .await?;
174
175        rows.into_iter()
176            .map(|row| Self::map(None, &row))
177            .try_collect_vec()
178    }
179}
180
181#[async_trait::async_trait]
182impl IntoAdminApiObject for LocationDatabaseHost {
183    type AdminApiObject = AdminApiLocationDatabaseHost;
184    type ExtraArgs<'a> = ();
185
186    async fn into_admin_api_object<'a>(
187        self,
188        state: &crate::State,
189        _args: Self::ExtraArgs<'a>,
190    ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
191        let api_object = AdminApiLocationDatabaseHost::init_hooks(&self, state).await?;
192
193        let api_object = finish_extendible!(
194            AdminApiLocationDatabaseHost {
195                database_host: self.database_host.into_admin_api_object(state, ()).await?,
196                created: self.created.and_utc(),
197            },
198            api_object,
199            state
200        )?;
201
202        Ok(api_object)
203    }
204}
205
206#[derive(ToSchema, Deserialize, Validate)]
207pub struct CreateLocationDatabaseHostOptions {
208    #[garde(skip)]
209    pub location_uuid: uuid::Uuid,
210    #[garde(skip)]
211    pub database_host_uuid: uuid::Uuid,
212}
213
214#[async_trait::async_trait]
215impl CreatableModel for LocationDatabaseHost {
216    type CreateOptions<'a> = CreateLocationDatabaseHostOptions;
217    type CreateResult = Self;
218
219    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
220        static CREATE_LISTENERS: LazyLock<CreateListenerList<LocationDatabaseHost>> =
221            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
222
223        &CREATE_LISTENERS
224    }
225
226    async fn create_with_transaction(
227        state: &crate::State,
228        mut options: Self::CreateOptions<'_>,
229        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
230    ) -> Result<Self, crate::database::DatabaseError> {
231        options.validate()?;
232
233        super::database_host::DatabaseHost::by_uuid_optional_cached(
234            &state.database,
235            options.database_host_uuid,
236        )
237        .await?
238        .ok_or(crate::database::InvalidRelationError("database_host"))?;
239
240        let mut query_builder = InsertQueryBuilder::new("location_database_hosts");
241
242        Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
243
244        query_builder
245            .set("location_uuid", options.location_uuid)
246            .set("database_host_uuid", options.database_host_uuid);
247
248        query_builder.execute(&mut **transaction).await?;
249
250        let mut result = match Self::by_location_uuid_database_host_uuid_with_transaction(
251            transaction,
252            options.location_uuid,
253            options.database_host_uuid,
254        )
255        .await?
256        {
257            Some(location_database_host) => location_database_host,
258            None => return Err(sqlx::Error::RowNotFound.into()),
259        };
260
261        Self::run_after_create_handlers(&mut result, &options, state, transaction).await?;
262
263        Ok(result)
264    }
265}
266
267#[async_trait::async_trait]
268impl DeletableModel for LocationDatabaseHost {
269    type DeleteOptions = ();
270
271    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
272        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<LocationDatabaseHost>> =
273            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
274
275        &DELETE_LISTENERS
276    }
277
278    async fn delete_with_transaction(
279        &self,
280        state: &crate::State,
281        options: Self::DeleteOptions,
282        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
283    ) -> Result<(), anyhow::Error> {
284        self.run_delete_handlers(&options, state, transaction)
285            .await?;
286
287        sqlx::query(
288            r#"
289            DELETE FROM location_database_hosts
290            WHERE location_database_hosts.location_uuid = $1 AND location_database_hosts.database_host_uuid = $2
291            "#,
292        )
293        .bind(self.location.uuid)
294        .bind(self.database_host.uuid)
295        .execute(&mut **transaction)
296        .await?;
297
298        self.run_after_delete_handlers(&options, state, transaction)
299            .await?;
300
301        Ok(())
302    }
303}
304
305#[schema_extension_derive::extendible]
306#[init_args(LocationDatabaseHost, crate::State)]
307#[hook_args(crate::State)]
308#[derive(ToSchema, Serialize)]
309#[schema(title = "LocationDatabaseHost")]
310pub struct AdminApiLocationDatabaseHost {
311    pub database_host: super::database_host::AdminApiDatabaseHost,
312
313    pub created: chrono::DateTime<chrono::Utc>,
314}