1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! Service layer for layers.

use diesel_async::AsyncPgConnection;
use reqwest::StatusCode;
use uuid::Uuid;

use crate::model::dto::actions::{ActionType, RestoreDrawingLayerActionPayload};
use crate::model::dto::drawings::DrawingDto;
use crate::model::dto::layers::UpdateLayerDto;
use crate::model::entity::drawings::Drawing;
use crate::model::entity::Map;
use crate::model::r#enum::layer_type::LayerType;
use crate::{
    config::data::SharedPool,
    error::ServiceError,
    model::{
        dto::layers::{LayerDto, LayerSearchParameters},
        entity::layers::Layer,
    },
};

/// Search layers from the database.
///
/// # Errors
/// If the connection to the database could not be established.
pub async fn find(
    search_parameters: LayerSearchParameters,
    pool: &SharedPool,
) -> Result<Vec<LayerDto>, ServiceError> {
    let mut conn = pool.get().await?;
    let result = Layer::find(search_parameters, &mut conn).await?;
    Ok(result)
}

/// Find a layer by id in the database.
///
/// # Errors
/// If the connection to the database could not be established.
pub async fn find_by_id(id: Uuid, pool: &SharedPool) -> Result<LayerDto, ServiceError> {
    let mut conn = pool.get().await?;
    let result = Layer::find_by_id(id, &mut conn).await?;
    Ok(result.into())
}

/// Create a new layer in the database.
///
/// # Errors
/// If the connection to the database could not be established.
pub async fn create(
    map_id: i32,
    new_layer: LayerDto,
    pool: &SharedPool,
) -> Result<LayerDto, ServiceError> {
    let mut conn = pool.get().await?;
    _ = Map::find_by_id(map_id, &mut conn).await?;
    let result = Layer::create(map_id, new_layer, &mut conn).await?;
    Ok(result)
}

/// Helper that returns an error if the given layer is not on the map.
///
/// # Errors
/// If the connection to the database could not be established.
async fn assert_layer_on_map(
    map_id: i32,
    layer_id: Uuid,
    conn: &mut AsyncPgConnection,
) -> Result<(), ServiceError> {
    let layer = Layer::find_by_id(layer_id, conn).await?;
    if layer.map_id != map_id {
        return Err(ServiceError::new(
            StatusCode::CONFLICT,
            "map_id of path and dto doesn't match",
        ));
    }
    Ok(())
}

/// Helper that returns an error in case `layer_ids` isn't exactly
/// the set of all non-deleted layers on the map.
///
/// # Errors
/// If the connection to the database could not be established.
async fn assert_all_layers_on_map(
    map_id: i32,
    layer_ids: &[Uuid],
    conn: &mut AsyncPgConnection,
) -> Result<(), ServiceError> {
    let layer_ids_in_map: Vec<Uuid> = Layer::find(
        LayerSearchParameters {
            map_id: Some(map_id),
            type_: None,
            is_alternative: None,
            only_non_deleted: Some(()),
        },
        conn,
    )
    .await?
    .into_iter()
    .map(|layer| layer.id)
    .collect();

    for layer_id in layer_ids {
        if !layer_ids_in_map.contains(layer_id) {
            return Err(ServiceError::new(
                StatusCode::CONFLICT,
                "map_id of path and dto doesn't match",
            ));
        }
    }
    Ok(())
}

/// Update layers. Available updates are:
///  - renaming of one layer
///  - reordering of multiple layers
///  - restoring of one layer that is makred deleted
///
/// # Errors
/// If the connection to the database could not be established.
pub async fn update(
    map_id: i32,
    update: UpdateLayerDto,
    pool: &SharedPool,
) -> Result<ActionType, ServiceError> {
    let mut conn = pool.get().await?;

    let action = match update {
        UpdateLayerDto::Rename(dto) => {
            assert_layer_on_map(map_id, dto.id, &mut conn).await?;
            Layer::rename(dto.clone(), &mut conn).await?;
            ActionType::RenameLayer(dto)
        }
        UpdateLayerDto::Reorder(new_order) => {
            assert_all_layers_on_map(map_id, &new_order, &mut conn).await?;
            Layer::reorder(new_order.clone(), &mut conn).await?;
            ActionType::ReorderLayers(new_order)
        }
        UpdateLayerDto::RestoreDrawingLayer(dto) => {
            assert_layer_on_map(map_id, dto.id, &mut conn).await?;
            let layer = Layer::find_by_id(dto.id, &mut conn).await?;
            if layer.marked_deleted.is_none() {
                return Err(ServiceError::new(
                    StatusCode::CONFLICT,
                    "layer is not marked deleted",
                ));
            }
            Layer::restore(map_id, dto.id, &mut conn).await?;
            let drawings = Drawing::find_in_layer(dto.id, &mut conn).await?;
            let drawing_dtos = drawings
                .into_iter()
                .map(DrawingDto::try_from)
                .collect::<Result<Vec<DrawingDto>, ServiceError>>()?;
            ActionType::RestoreDrawingLayer(RestoreDrawingLayerActionPayload::new(
                dto.id,
                drawing_dtos,
            ))
        }
    };
    Ok(action)
}

/// Delete the layer in the database.
///
/// # Errors
/// If the connection to the database could not be established.
pub async fn delete_by_id(
    map_id: i32,
    layer_id: Uuid,
    pool: &SharedPool,
) -> Result<(), ServiceError> {
    let mut conn = pool.get().await?;
    let layer = Layer::find_by_id(layer_id, &mut conn).await?;
    if layer.type_ != LayerType::Drawing {
        return Err(ServiceError::new(
            StatusCode::FORBIDDEN,
            "can only delete drawing layer",
        ));
    }
    Layer::delete(map_id, layer_id, &mut conn).await?;
    Ok(())
}