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}