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