Skip to main content

shared/models/
node_tunnel.rs

1use crate::{
2    models::{InsertQueryBuilder, UpdateQueryBuilder},
3    prelude::*,
4};
5use garde::Validate;
6use serde::{Deserialize, Serialize};
7use sqlx::{Row, postgres::PgRow};
8use std::{
9    collections::BTreeMap,
10    sync::{Arc, LazyLock},
11};
12use tundra_common::hash::Hash32;
13use utoipa::ToSchema;
14
15#[derive(Serialize, Deserialize)]
16pub struct NodeTunnel {
17    pub node: Fetchable<super::node::Node>,
18
19    pub host: compact_str::CompactString,
20    pub port: u16,
21
22    pub cert_sha256: Option<Hash32>,
23
24    pub created: chrono::NaiveDateTime,
25
26    extension_data: super::ModelExtensionData,
27}
28
29impl BaseModel for NodeTunnel {
30    const NAME: &'static str = "node_tunnel";
31
32    fn get_extension_list() -> &'static super::ModelExtensionList {
33        static EXTENSIONS: LazyLock<super::ModelExtensionList> =
34            LazyLock::new(|| parking_lot::RwLock::new(Vec::new()));
35
36        &EXTENSIONS
37    }
38
39    fn get_extension_data(&self) -> &super::ModelExtensionData {
40        &self.extension_data
41    }
42
43    #[inline]
44    fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
45        let prefix = prefix.unwrap_or_default();
46
47        BTreeMap::from([
48            (
49                "node_tunnels.node_uuid",
50                compact_str::format_compact!("{prefix}node_uuid"),
51            ),
52            (
53                "node_tunnels.host",
54                compact_str::format_compact!("{prefix}host"),
55            ),
56            (
57                "node_tunnels.port",
58                compact_str::format_compact!("{prefix}port"),
59            ),
60            (
61                "node_tunnels.cert_sha256",
62                compact_str::format_compact!("{prefix}cert_sha256"),
63            ),
64            (
65                "node_tunnels.created",
66                compact_str::format_compact!("{prefix}created"),
67            ),
68        ])
69    }
70
71    #[inline]
72    fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
73        let prefix = prefix.unwrap_or_default();
74
75        Ok(Self {
76            node: super::node::Node::get_fetchable(
77                row.try_get(compact_str::format_compact!("{prefix}node_uuid").as_str())?,
78            ),
79            host: row.try_get(compact_str::format_compact!("{prefix}host").as_str())?,
80            port: row.try_get::<i32, _>(compact_str::format_compact!("{prefix}port").as_str())?
81                as u16,
82            cert_sha256: row
83                .try_get::<Option<Vec<u8>>, _>(
84                    compact_str::format_compact!("{prefix}cert_sha256").as_str(),
85                )?
86                .and_then(|bytes| <[u8; 32]>::try_from(bytes.as_slice()).ok())
87                .map(Hash32),
88            created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
89            extension_data: Self::map_extensions(prefix, row)?,
90        })
91    }
92}
93
94impl NodeTunnel {
95    pub async fn by_node_uuid(
96        database: &crate::database::Database,
97        node_uuid: uuid::Uuid,
98    ) -> Result<Option<Self>, crate::database::DatabaseError> {
99        let row = sqlx::query(sqlx::AssertSqlSafe(format!(
100            r#"
101            SELECT {}
102            FROM node_tunnels
103            WHERE node_tunnels.node_uuid = $1
104            "#,
105            Self::columns_sql(None)
106        )))
107        .bind(node_uuid)
108        .fetch_optional(database.read())
109        .await?;
110
111        row.try_map(|row| Self::map(None, &row))
112    }
113
114    pub async fn store_cert(
115        database: &crate::database::Database,
116        node_uuid: uuid::Uuid,
117        cert_sha256: Option<Hash32>,
118    ) -> Result<bool, crate::database::DatabaseError> {
119        let mut transaction = database.write().begin().await?;
120
121        let affected = sqlx::query(
122            r#"
123            UPDATE node_tunnels
124            SET cert_sha256 = $2
125            WHERE node_tunnels.node_uuid = $1
126            "#,
127        )
128        .bind(node_uuid)
129        .bind(cert_sha256.map(|hash| hash.0.to_vec()))
130        .execute(&mut *transaction)
131        .await?
132        .rows_affected();
133
134        if affected == 0 {
135            return Ok(false);
136        }
137
138        crate::tunnel::bump_epoch(&mut *transaction).await?;
139        transaction.commit().await?;
140
141        Ok(true)
142    }
143}
144
145#[async_trait::async_trait]
146impl IntoAdminApiObject for NodeTunnel {
147    type AdminApiObject = AdminApiNodeTunnel;
148    type ExtraArgs<'a> = ();
149
150    async fn into_admin_api_object<'a>(
151        self,
152        state: &crate::State,
153        _args: Self::ExtraArgs<'a>,
154    ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
155        let api_object = AdminApiNodeTunnel::init_hooks(&self, state).await?;
156
157        let api_object = finish_extendible!(
158            AdminApiNodeTunnel {
159                host: self.host,
160                port: self.port,
161                cert_sha256: self.cert_sha256.map(|hash| hash.to_hex().into()),
162                created: self.created.and_utc(),
163            },
164            api_object,
165            state
166        )?;
167
168        Ok(api_object)
169    }
170}
171
172#[schema_extension_derive::extendible]
173#[init_args(NodeTunnel, crate::State)]
174#[hook_args(crate::State)]
175#[derive(ToSchema, Serialize)]
176#[schema(title = "AdminNodeTunnel")]
177pub struct AdminApiNodeTunnel {
178    pub host: compact_str::CompactString,
179    pub port: u16,
180
181    pub cert_sha256: Option<compact_str::CompactString>,
182
183    pub created: chrono::DateTime<chrono::Utc>,
184}
185
186#[derive(ToSchema, Deserialize, Validate)]
187pub struct CreateNodeTunnelOptions {
188    #[garde(skip)]
189    pub node_uuid: uuid::Uuid,
190
191    #[garde(length(chars, min = 1, max = 255))]
192    #[schema(min_length = 1, max_length = 255)]
193    pub host: compact_str::CompactString,
194    #[garde(range(min = 1))]
195    #[schema(minimum = 1)]
196    pub port: u16,
197}
198
199#[async_trait::async_trait]
200impl CreatableModel for NodeTunnel {
201    type CreateOptions<'a> = CreateNodeTunnelOptions;
202    type CreateResult = Self;
203
204    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
205        static CREATE_LISTENERS: LazyLock<CreateListenerList<NodeTunnel>> =
206            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
207
208        &CREATE_LISTENERS
209    }
210
211    async fn create_with_transaction(
212        state: &crate::State,
213        mut options: Self::CreateOptions<'_>,
214        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
215    ) -> Result<Self, crate::database::DatabaseError> {
216        options.validate()?;
217
218        let mut query_builder = InsertQueryBuilder::new("node_tunnels");
219
220        Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
221
222        query_builder
223            .set("node_uuid", options.node_uuid)
224            .set("host", &options.host)
225            .set("port", options.port as i32);
226
227        let row = query_builder
228            .returning(&Self::columns_sql(None))
229            .fetch_one(&mut **transaction)
230            .await?;
231        let mut node_tunnel = Self::map(None, &row)?;
232
233        crate::tunnel::bump_epoch(&mut **transaction).await?;
234
235        Self::run_after_create_handlers(&mut node_tunnel, &options, state, transaction).await?;
236
237        Ok(node_tunnel)
238    }
239}
240
241#[derive(ToSchema, Serialize, Deserialize, Validate, Default)]
242pub struct UpdateNodeTunnelOptions {
243    #[garde(length(chars, min = 1, max = 255))]
244    #[schema(min_length = 1, max_length = 255)]
245    pub host: Option<compact_str::CompactString>,
246    #[garde(range(min = 1))]
247    #[schema(minimum = 1)]
248    pub port: Option<u16>,
249}
250
251#[async_trait::async_trait]
252impl UpdatableModel for NodeTunnel {
253    type UpdateOptions = UpdateNodeTunnelOptions;
254
255    fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
256        static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<NodeTunnel>> =
257            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
258
259        &UPDATE_LISTENERS
260    }
261
262    async fn update_with_transaction(
263        &mut self,
264        state: &crate::State,
265        mut options: Self::UpdateOptions,
266        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
267    ) -> Result<(), crate::database::DatabaseError> {
268        options.validate()?;
269
270        let mut query_builder = UpdateQueryBuilder::new("node_tunnels");
271
272        self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
273            .await?;
274
275        query_builder
276            .set("host", options.host.as_ref())
277            .set("port", options.port.map(|port| port as i32))
278            .where_eq("node_uuid", self.node.uuid);
279
280        query_builder.execute(&mut **transaction).await?;
281
282        crate::tunnel::bump_epoch(&mut **transaction).await?;
283
284        if let Some(host) = options.host {
285            self.host = host;
286        }
287        if let Some(port) = options.port {
288            self.port = port;
289        }
290
291        self.run_after_update_handlers(state, transaction).await?;
292
293        Ok(())
294    }
295}
296
297#[async_trait::async_trait]
298impl DeletableModel for NodeTunnel {
299    type DeleteOptions = ();
300
301    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
302        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<NodeTunnel>> =
303            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
304
305        &DELETE_LISTENERS
306    }
307
308    async fn delete_with_transaction(
309        &self,
310        state: &crate::State,
311        options: Self::DeleteOptions,
312        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
313    ) -> Result<(), anyhow::Error> {
314        self.run_delete_handlers(&options, state, transaction)
315            .await?;
316
317        sqlx::query(
318            r#"
319            DELETE FROM node_tunnels
320            WHERE node_tunnels.node_uuid = $1
321            "#,
322        )
323        .bind(self.node.uuid)
324        .execute(&mut **transaction)
325        .await?;
326
327        crate::tunnel::bump_epoch(&mut **transaction).await?;
328
329        self.run_after_delete_handlers(&options, state, transaction)
330            .await?;
331
332        Ok(())
333    }
334}