backend/model/entity/
layers_impl.rs

1//! Contains the implementation of [`Layer`].
2
3use chrono::Utc;
4use diesel::pg::Pg;
5use diesel::{debug_query, sql_query, ExpressionMethods, QueryDsl, QueryResult};
6use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
7use futures::future::try_join_all;
8use futures::Future;
9use log::debug;
10use uuid::Uuid;
11
12use crate::{
13    model::{
14        dto::layers::{LayerDto, LayerRenameDto, LayerSearchParameters},
15        entity::layers::{Layer, UpdateLayerMarkedDeleted, UpdateLayerName, UpdateLayerOrderIndex},
16    },
17    schema::layers,
18};
19
20impl Layer {
21    /// Get a page of layers.
22    /// Can be filtered by its active status if one is provided in `search_parameters`.
23    ///
24    /// # Errors
25    /// * Unknown, diesel doesn't say why it might error.
26    pub async fn find(
27        search_parameters: LayerSearchParameters,
28        conn: &mut AsyncPgConnection,
29    ) -> QueryResult<Vec<LayerDto>> {
30        let mut query = layers::table.select(layers::all_columns).into_boxed();
31
32        if let Some(map_id_search) = search_parameters.map_id {
33            query = query.filter(layers::map_id.eq(map_id_search));
34        }
35        if let Some(type_search) = search_parameters.type_ {
36            query = query.filter(layers::type_.eq(type_search));
37        }
38        if let Some(is_alternative_search) = search_parameters.is_alternative {
39            query = query.filter(layers::is_alternative.eq(is_alternative_search));
40        }
41
42        if search_parameters.only_non_deleted.is_some() {
43            query = query.filter(layers::marked_deleted.is_null());
44        }
45
46        query = query.order((layers::order_index, layers::marked_deleted.desc()));
47
48        debug!("{}", debug_query::<Pg, _>(&query));
49        Ok(query
50            .load::<Self>(conn)
51            .await?
52            .into_iter()
53            .map(Into::into)
54            .collect())
55    }
56
57    /// Fetch layer by id from the database.
58    ///
59    /// # Errors
60    /// * Unknown, diesel doesn't say why it might error.
61    pub async fn find_by_id(id: Uuid, conn: &mut AsyncPgConnection) -> QueryResult<Self> {
62        let query = layers::table.find(id);
63        debug!("{}", debug_query::<Pg, _>(&query));
64        query.first::<Self>(conn).await
65    }
66
67    /// Defer checking of the unique constraint (`map_id`, `order_index`, `marked_deleted`)
68    /// until the end of the current transaction.
69    ///
70    /// # Errors
71    /// * Unknown, diesel doesn't say why it might error.
72    async fn defer_unique_order_index_constraint(
73        transaction: &mut AsyncPgConnection,
74    ) -> QueryResult<()> {
75        sql_query("SET CONSTRAINTS layers_map_id_order_index_unique DEFERRED")
76            .execute(transaction)
77            .await?;
78        Ok(())
79    }
80
81    /// Helper to increment or decrement  all layers with `order_index`
82    /// greater or equal the provided index. This is used when layers get
83    /// deleted/restored to shift all subsequent layers up/down one place.
84    ///
85    /// # Errors
86    /// * Unknown, diesel doesn't say why it might error.
87    async fn shift_order_indices_by(
88        map_id: i32,
89        order_index: i32,
90        increment: bool,
91        transaction: &mut AsyncPgConnection,
92    ) -> QueryResult<()> {
93        let layer_ids = Self::find(
94            LayerSearchParameters {
95                is_alternative: None,
96                map_id: Some(map_id),
97                type_: None,
98                only_non_deleted: Some(()),
99            },
100            transaction,
101        )
102        .await?;
103        let updates = layer_ids
104            .into_iter()
105            .filter(|l| l.order_index >= order_index)
106            .map(|l| UpdateLayerOrderIndex {
107                id: l.id,
108                order_index: l.order_index + if increment { 1 } else { -1 },
109            })
110            .collect();
111        let futures = Self::do_order_update(updates, transaction);
112        try_join_all(futures).await?;
113        Ok(())
114    }
115
116    /// Rename a layer in the database.
117    ///
118    /// # Errors
119    /// * Unknown, diesel doesn't say why it might error.
120    pub async fn create(
121        map_id: i32,
122        new_layer: LayerDto,
123        conn: &mut AsyncPgConnection,
124    ) -> QueryResult<LayerDto> {
125        let new_order_index = new_layer.order_index;
126        let new_layer = Self::from((map_id, new_layer));
127        let query = diesel::insert_into(layers::table).values(&new_layer);
128
129        let created_layer = conn
130            .transaction(|transaction| {
131                Box::pin(async move {
132                    Self::shift_order_indices_by(map_id, new_order_index, true, transaction)
133                        .await?;
134                    debug!("{}", debug_query::<Pg, _>(&query));
135                    let layer_dto = query.get_result::<Self>(transaction).await?.into();
136                    Ok::<LayerDto, diesel::result::Error>(layer_dto)
137                })
138            })
139            .await?;
140        Ok(created_layer)
141    }
142
143    /// Reorder multiple layers in the database. That means
144    /// setting all `order_index` fields.
145    ///
146    /// # Errors
147    /// * Unknown, diesel doesn't say why it might error.
148    pub async fn reorder(new_order: Vec<Uuid>, conn: &mut AsyncPgConnection) -> QueryResult<()> {
149        let order_updates: Vec<UpdateLayerOrderIndex> = new_order
150            .into_iter()
151            .zip(0..)
152            .map(|(id, order_index)| UpdateLayerOrderIndex { id, order_index })
153            .collect();
154
155        conn.transaction(|transaction| {
156            Box::pin(async move {
157                // During the reordering process, the unique constraint on (map_id, order_index)
158                // may be temporarily violated.
159                // The constraint will be enforced when the transaction is finalized.
160                Self::defer_unique_order_index_constraint(transaction).await?;
161                let futures = Self::do_order_update(order_updates, transaction);
162                try_join_all(futures).await?;
163                Ok::<(), diesel::result::Error>(())
164            })
165        })
166        .await?;
167
168        Ok(())
169    }
170
171    /// This helper function is needed, with explicit type annotations.
172    fn do_order_update(
173        updates: Vec<UpdateLayerOrderIndex>,
174        conn: &mut AsyncPgConnection,
175    ) -> Vec<impl Future<Output = QueryResult<usize>>> {
176        updates
177            .into_iter()
178            .map(|update| {
179                diesel::update(layers::table.find(update.id))
180                    .set(update)
181                    .execute(conn)
182            })
183            .collect()
184    }
185
186    /// Rename layers in the database.
187    ///
188    /// # Errors
189    /// * Unknown, diesel doesn't say why it might error.
190    pub async fn rename(dto: LayerRenameDto, conn: &mut AsyncPgConnection) -> QueryResult<()> {
191        let update: UpdateLayerName = dto.into();
192        let query = diesel::update(layers::table.find(update.id)).set(&update);
193        debug!("{}", debug_query::<Pg, _>(&query));
194        query.execute(conn).await?;
195        Ok(())
196    }
197
198    /// Soft-delete or restoring layers in the database by settings the `marked_deleted` field.
199    ///
200    /// # Errors
201    /// * Unknown, diesel doesn't say why it might error.
202    async fn set_marked_deleted(
203        drawing_layer_id: Uuid,
204        marked_deleted: bool,
205        conn: &mut AsyncPgConnection,
206    ) -> QueryResult<Self> {
207        let marked_deleted_timestamp = marked_deleted.then(|| Utc::now().naive_utc());
208        diesel::update(layers::table.find(drawing_layer_id))
209            .set(UpdateLayerMarkedDeleted {
210                id: drawing_layer_id,
211                marked_deleted: marked_deleted_timestamp,
212            })
213            .get_result::<Self>(conn)
214            .await
215    }
216
217    /// Soft-delete a layers in the database by settings the `marked_deleted` field to the current date.
218    /// All subsequent layers are moved one `order_index` down.
219    ///
220    /// # Errors
221    /// * Unknown, diesel doesn't say why it might error.
222    pub async fn delete(
223        map_id: i32,
224        layer_id: Uuid,
225        conn: &mut AsyncPgConnection,
226    ) -> QueryResult<()> {
227        conn.transaction(|transaction| {
228            Box::pin(async move {
229                let layer = Self::set_marked_deleted(layer_id, true, transaction).await?;
230                Self::shift_order_indices_by(map_id, layer.order_index, false, transaction).await?;
231                Ok(())
232            })
233        })
234        .await
235    }
236
237    /// Restore a layers that has been marked deleted in the database.
238    /// The layer takes on its previous `order_index` and all subsequent
239    /// layers are moved one layer down.
240    ///
241    /// # Errors
242    /// * Unknown, diesel doesn't say why it might error.
243    pub async fn restore(
244        map_id: i32,
245        layer_id: Uuid,
246        conn: &mut AsyncPgConnection,
247    ) -> QueryResult<()> {
248        conn.transaction(|transaction| {
249            Box::pin(async move {
250                Self::defer_unique_order_index_constraint(transaction).await?;
251                let layer = Self::find_by_id(layer_id, transaction).await?;
252                Self::shift_order_indices_by(map_id, layer.order_index, true, transaction).await?;
253                _ = Self::set_marked_deleted(layer_id, false, transaction).await?;
254                Ok(())
255            })
256        })
257        .await
258    }
259}