shared/models/
location_database_host.rs1use 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
19impl BaseModel for LocationDatabaseHost {
20 const NAME: &'static str = "location_database_host";
21
22 #[inline]
23 fn columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
24 let prefix = prefix.unwrap_or_default();
25
26 let mut columns = BTreeMap::from([
27 (
28 "location_database_hosts.location_uuid",
29 compact_str::format_compact!("{prefix}location_uuid"),
30 ),
31 (
32 "location_database_hosts.created",
33 compact_str::format_compact!("{prefix}created"),
34 ),
35 ]);
36
37 columns.extend(super::database_host::DatabaseHost::columns(Some(
38 "database_host_",
39 )));
40
41 columns
42 }
43
44 #[inline]
45 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
46 let prefix = prefix.unwrap_or_default();
47
48 Ok(Self {
49 location: super::location::Location::get_fetchable(
50 row.try_get(compact_str::format_compact!("{prefix}location_uuid").as_str())?,
51 ),
52 database_host: super::database_host::DatabaseHost::map(Some("database_host_"), row)?,
53 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
54 })
55 }
56}
57
58impl LocationDatabaseHost {
59 pub async fn by_location_uuid_database_host_uuid(
60 database: &crate::database::Database,
61 location_uuid: uuid::Uuid,
62 database_host_uuid: uuid::Uuid,
63 ) -> Result<Option<Self>, crate::database::DatabaseError> {
64 let row = sqlx::query(&format!(
65 r#"
66 SELECT {}
67 FROM location_database_hosts
68 JOIN database_hosts ON location_database_hosts.database_host_uuid = database_hosts.uuid
69 WHERE location_database_hosts.location_uuid = $1 AND location_database_hosts.database_host_uuid = $2
70 "#,
71 Self::columns_sql(None)
72 ))
73 .bind(location_uuid)
74 .bind(database_host_uuid)
75 .fetch_optional(database.read())
76 .await?;
77
78 row.try_map(|row| Self::map(None, &row))
79 }
80
81 pub async fn by_location_uuid_with_pagination(
82 database: &crate::database::Database,
83 location_uuid: uuid::Uuid,
84 page: i64,
85 per_page: i64,
86 search: Option<&str>,
87 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
88 let offset = (page - 1) * per_page;
89
90 let rows = sqlx::query(&format!(
91 r#"
92 SELECT {}, COUNT(*) OVER() AS total_count
93 FROM location_database_hosts
94 JOIN database_hosts ON location_database_hosts.database_host_uuid = database_hosts.uuid
95 WHERE location_database_hosts.location_uuid = $1 AND ($2 IS NULL OR database_hosts.name ILIKE '%' || $2 || '%')
96 ORDER BY location_database_hosts.created
97 LIMIT $3 OFFSET $4
98 "#,
99 Self::columns_sql(None)
100 ))
101 .bind(location_uuid)
102 .bind(search)
103 .bind(per_page)
104 .bind(offset)
105 .fetch_all(database.read())
106 .await?;
107
108 Ok(super::Pagination {
109 total: rows
110 .first()
111 .map_or(Ok(0), |row| row.try_get("total_count"))?,
112 per_page,
113 page,
114 data: rows
115 .into_iter()
116 .map(|row| Self::map(None, &row))
117 .try_collect_vec()?,
118 })
119 }
120
121 pub async fn all_public_by_location_uuid(
122 database: &crate::database::Database,
123 location_uuid: uuid::Uuid,
124 ) -> Result<Vec<Self>, crate::database::DatabaseError> {
125 let rows = sqlx::query(&format!(
126 r#"
127 SELECT {}
128 FROM location_database_hosts
129 JOIN database_hosts ON location_database_hosts.database_host_uuid = database_hosts.uuid
130 WHERE location_database_hosts.location_uuid = $1 AND database_hosts.deployment_enabled
131 ORDER BY location_database_hosts.created DESC
132 "#,
133 Self::columns_sql(None)
134 ))
135 .bind(location_uuid)
136 .fetch_all(database.read())
137 .await?;
138
139 rows.into_iter()
140 .map(|row| Self::map(None, &row))
141 .try_collect_vec()
142 }
143
144 #[inline]
145 pub fn into_admin_api_object(self) -> AdminApiLocationDatabaseHost {
146 AdminApiLocationDatabaseHost {
147 database_host: self.database_host.into_admin_api_object(),
148 created: self.created.and_utc(),
149 }
150 }
151}
152
153#[derive(ToSchema, Deserialize, Validate)]
154pub struct CreateLocationDatabaseHostOptions {
155 #[garde(skip)]
156 pub location_uuid: uuid::Uuid,
157 #[garde(skip)]
158 pub database_host_uuid: uuid::Uuid,
159}
160
161#[async_trait::async_trait]
162impl CreatableModel for LocationDatabaseHost {
163 type CreateOptions<'a> = CreateLocationDatabaseHostOptions;
164 type CreateResult = Self;
165
166 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
167 static CREATE_LISTENERS: LazyLock<CreateListenerList<LocationDatabaseHost>> =
168 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
169
170 &CREATE_LISTENERS
171 }
172
173 async fn create(
174 state: &crate::State,
175 mut options: Self::CreateOptions<'_>,
176 ) -> Result<Self, crate::database::DatabaseError> {
177 options.validate()?;
178
179 super::database_host::DatabaseHost::by_uuid_optional_cached(
180 &state.database,
181 options.database_host_uuid,
182 )
183 .await?
184 .ok_or(crate::database::InvalidRelationError("database_host"))?;
185
186 let mut transaction = state.database.write().begin().await?;
187
188 let mut query_builder = InsertQueryBuilder::new("location_database_hosts");
189
190 Self::run_create_handlers(&mut options, &mut query_builder, state, &mut transaction)
191 .await?;
192
193 query_builder
194 .set("location_uuid", options.location_uuid)
195 .set("database_host_uuid", options.database_host_uuid);
196
197 query_builder.execute(&mut *transaction).await?;
198
199 transaction.commit().await?;
200
201 match Self::by_location_uuid_database_host_uuid(
202 &state.database,
203 options.location_uuid,
204 options.database_host_uuid,
205 )
206 .await?
207 {
208 Some(location_database_host) => Ok(location_database_host),
209 None => Err(sqlx::Error::RowNotFound.into()),
210 }
211 }
212}
213
214#[async_trait::async_trait]
215impl DeletableModel for LocationDatabaseHost {
216 type DeleteOptions = ();
217
218 fn get_delete_handlers() -> &'static LazyLock<DeleteListenerList<Self>> {
219 static DELETE_LISTENERS: LazyLock<DeleteListenerList<LocationDatabaseHost>> =
220 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
221
222 &DELETE_LISTENERS
223 }
224
225 async fn delete(
226 &self,
227 state: &crate::State,
228 options: Self::DeleteOptions,
229 ) -> Result<(), anyhow::Error> {
230 let mut transaction = state.database.write().begin().await?;
231
232 self.run_delete_handlers(&options, state, &mut transaction)
233 .await?;
234
235 sqlx::query(
236 r#"
237 DELETE FROM location_database_hosts
238 WHERE location_database_hosts.location_uuid = $1 AND location_database_hosts.database_host_uuid = $2
239 "#,
240 )
241 .bind(self.location.uuid)
242 .bind(self.database_host.uuid)
243 .execute(&mut *transaction)
244 .await?;
245
246 transaction.commit().await?;
247
248 Ok(())
249 }
250}
251
252#[derive(ToSchema, Serialize)]
253#[schema(title = "LocationDatabaseHost")]
254pub struct AdminApiLocationDatabaseHost {
255 pub database_host: super::database_host::AdminApiDatabaseHost,
256
257 pub created: chrono::DateTime<chrono::Utc>,
258}