Skip to main content

shared/models/
nest_egg_device.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)]
12pub struct NestEggDevice {
13    pub device: Fetchable<super::device::Device>,
14    pub nest_egg: Fetchable<super::nest_egg::NestEgg>,
15
16    pub created: chrono::NaiveDateTime,
17
18    extension_data: super::ModelExtensionData,
19}
20
21impl BaseModel for NestEggDevice {
22    const NAME: &'static str = "nest_egg_device";
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        BTreeMap::from([
40            (
41                "nest_egg_devices.device_uuid",
42                compact_str::format_compact!("{prefix}device_uuid"),
43            ),
44            (
45                "nest_egg_devices.egg_uuid",
46                compact_str::format_compact!("{prefix}egg_uuid"),
47            ),
48            (
49                "nest_egg_devices.created",
50                compact_str::format_compact!("{prefix}created"),
51            ),
52        ])
53    }
54
55    #[inline]
56    fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
57        let prefix = prefix.unwrap_or_default();
58
59        Ok(Self {
60            device: super::device::Device::get_fetchable(
61                row.try_get(compact_str::format_compact!("{prefix}device_uuid").as_str())?,
62            ),
63            nest_egg: super::nest_egg::NestEgg::get_fetchable(
64                row.try_get(compact_str::format_compact!("{prefix}egg_uuid").as_str())?,
65            ),
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 NestEggDevice {
73    pub async fn by_egg_uuid_device_uuid(
74        database: &crate::database::Database,
75        egg_uuid: uuid::Uuid,
76        device_uuid: uuid::Uuid,
77    ) -> Result<Option<Self>, crate::database::DatabaseError> {
78        let row = sqlx::query(sqlx::AssertSqlSafe(format!(
79            r#"
80            SELECT {}
81            FROM nest_egg_devices
82            WHERE nest_egg_devices.egg_uuid = $1 AND nest_egg_devices.device_uuid = $2
83            "#,
84            Self::columns_sql(None)
85        )))
86        .bind(egg_uuid)
87        .bind(device_uuid)
88        .fetch_optional(database.read())
89        .await?;
90
91        row.try_map(|row| Self::map(None, &row))
92    }
93
94    pub async fn by_egg_uuid_with_pagination(
95        database: &crate::database::Database,
96        egg_uuid: uuid::Uuid,
97        page: i64,
98        per_page: i64,
99        search: Option<&str>,
100    ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
101        super::PaginatedQueryBuilder::new(
102            Self::columns_sql(None),
103            format!(
104                r#"
105                FROM nest_egg_devices
106                JOIN devices ON devices.uuid = nest_egg_devices.device_uuid
107                WHERE nest_egg_devices.egg_uuid = $1 AND {search}
108                "#,
109                search = super::search_sql(2, &["devices.name"], &["devices.uuid"]),
110            ),
111            "nest_egg_devices.created, nest_egg_devices.egg_uuid, nest_egg_devices.device_uuid",
112        )
113        .bind(egg_uuid)
114        .bind(search)
115        .fetch(database, page, per_page)
116        .await
117    }
118
119    pub async fn by_device_uuid_with_pagination(
120        database: &crate::database::Database,
121        device_uuid: uuid::Uuid,
122        page: i64,
123        per_page: i64,
124        search: Option<&str>,
125    ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
126        super::PaginatedQueryBuilder::new(
127            Self::columns_sql(None),
128            format!(
129                r#"
130                FROM nest_egg_devices
131                JOIN nest_eggs ON nest_eggs.uuid = nest_egg_devices.egg_uuid
132                WHERE nest_egg_devices.device_uuid = $1 AND {search}
133                "#,
134                search = super::search_sql(2, &["nest_eggs.name"], &["nest_eggs.uuid"]),
135            ),
136            "nest_egg_devices.created, nest_egg_devices.egg_uuid, nest_egg_devices.device_uuid",
137        )
138        .bind(device_uuid)
139        .bind(search)
140        .fetch(database, page, per_page)
141        .await
142    }
143
144    pub async fn into_admin_nest_egg_api_object(
145        self,
146        state: &crate::State,
147        _args: (),
148    ) -> Result<AdminApiNestEggNestEggDevice, crate::database::DatabaseError> {
149        let nest_egg = self.nest_egg.fetch_cached(&state.database).await?;
150        let nest = nest_egg.nest.clone();
151        let (nest, nest_egg) = tokio::try_join!(nest.fetch_cached(&state.database), async {
152            Ok(nest_egg.into_admin_api_object(state, ()).await?)
153        })?;
154
155        Ok(AdminApiNestEggNestEggDevice {
156            nest: nest.into_admin_api_object(state, ()).await?,
157            nest_egg,
158            created: self.created.and_utc(),
159        })
160    }
161}
162
163#[async_trait::async_trait]
164impl IntoAdminApiObject for NestEggDevice {
165    type AdminApiObject = AdminApiNestEggDevice;
166    type ExtraArgs<'a> = ();
167
168    async fn into_admin_api_object<'a>(
169        self,
170        state: &crate::State,
171        _args: Self::ExtraArgs<'a>,
172    ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
173        let api_object = AdminApiNestEggDevice::init_hooks(&self, state).await?;
174
175        let api_object = finish_extendible!(
176            AdminApiNestEggDevice {
177                device: self
178                    .device
179                    .fetch_cached(&state.database)
180                    .await?
181                    .into_admin_api_object(state, ())
182                    .await?,
183                created: self.created.and_utc(),
184            },
185            api_object,
186            state
187        )?;
188
189        Ok(api_object)
190    }
191}
192
193#[derive(ToSchema, Deserialize, Validate)]
194pub struct CreateNestEggDeviceOptions {
195    #[garde(skip)]
196    pub egg_uuid: uuid::Uuid,
197    #[garde(skip)]
198    pub device_uuid: uuid::Uuid,
199}
200
201#[async_trait::async_trait]
202impl CreatableModel for NestEggDevice {
203    type CreateOptions<'a> = CreateNestEggDeviceOptions;
204    type CreateResult = Self;
205
206    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
207        static CREATE_LISTENERS: LazyLock<CreateListenerList<NestEggDevice>> =
208            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
209
210        &CREATE_LISTENERS
211    }
212
213    async fn create_with_transaction(
214        state: &crate::State,
215        mut options: Self::CreateOptions<'_>,
216        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
217    ) -> Result<Self, crate::database::DatabaseError> {
218        options.validate()?;
219
220        super::device::Device::by_uuid_optional_cached(&state.database, options.device_uuid)
221            .await?
222            .ok_or(crate::database::InvalidRelationError("device"))?;
223
224        let mut query_builder = InsertQueryBuilder::new("nest_egg_devices");
225
226        Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
227
228        query_builder
229            .set("egg_uuid", options.egg_uuid)
230            .set("device_uuid", options.device_uuid);
231
232        let row = query_builder
233            .returning(&Self::columns_sql(None))
234            .fetch_one(&mut **transaction)
235            .await?;
236        let mut nest_egg_device = Self::map(None, &row)?;
237
238        Self::run_after_create_handlers(&mut nest_egg_device, &options, state, transaction).await?;
239
240        Ok(nest_egg_device)
241    }
242}
243
244#[async_trait::async_trait]
245impl DeletableModel for NestEggDevice {
246    type DeleteOptions = ();
247
248    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
249        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<NestEggDevice>> =
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 nest_egg_devices
267            WHERE nest_egg_devices.egg_uuid = $1 AND nest_egg_devices.device_uuid = $2
268            "#,
269        )
270        .bind(self.nest_egg.uuid)
271        .bind(self.device.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#[derive(ToSchema, Serialize)]
283#[schema(title = "AdminNestEggNestEggDevice")]
284pub struct AdminApiNestEggNestEggDevice {
285    pub nest: super::nest::AdminApiNest,
286    pub nest_egg: super::nest_egg::AdminApiNestEgg,
287
288    pub created: chrono::DateTime<chrono::Utc>,
289}
290
291#[schema_extension_derive::extendible]
292#[init_args(NestEggDevice, crate::State)]
293#[hook_args(crate::State)]
294#[derive(ToSchema, Serialize)]
295#[schema(title = "AdminNestEggDevice")]
296pub struct AdminApiNestEggDevice {
297    pub device: super::device::AdminApiDevice,
298
299    pub created: chrono::DateTime<chrono::Utc>,
300}