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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
//! Service layer for maps.

use actix_http::StatusCode;
use postgis_diesel::types::{Point, Polygon};
use uuid::Uuid;

use crate::{
    config::data::SharedPool,
    error::ServiceError,
    model::{
        dto::{
            base_layer_images::BaseLayerImageDto, layers::LayerDto, MapDto, MapSearchParameters,
            NewMapDto, Page, PageParameters, UpdateMapDto, UpdateMapGeometryDto,
        },
        entity::{base_layer_images::BaseLayerImages, layers::Layer, Map},
        r#enum::layer_type::LayerType,
    },
};

/// Defines which layers should be created when a new map is created.
const LAYER_TYPES: [LayerType; 6] = [
    LayerType::Base,
    LayerType::Drawing,
    LayerType::Soiltexture,
    LayerType::Hydrology,
    LayerType::Shade,
    LayerType::Plants,
];

/// Search maps from the database.
///
/// # Errors
/// If the connection to the database could not be established.
pub async fn find(
    search_parameters: MapSearchParameters,
    page_parameters: PageParameters,
    pool: &SharedPool,
) -> Result<Page<MapDto>, ServiceError> {
    let mut conn = pool.get().await?;
    let result = Map::find(search_parameters, page_parameters, &mut conn).await?;
    Ok(result)
}

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

/// Create a new map in the database.
///
/// # Errors
/// If the connection to the database could not be established.
pub async fn create(
    new_map: NewMapDto,
    user_id: Uuid,
    pool: &SharedPool,
) -> Result<MapDto, ServiceError> {
    let mut conn = pool.get().await?;

    if Map::is_name_taken(&new_map.name, &mut conn).await? {
        return Err(ServiceError::new(
            StatusCode::CONFLICT,
            "Map name already taken",
        ));
    }

    let geometry_validation_result = is_valid_map_geometry(&new_map.geometry);
    if let Some(error) = geometry_validation_result {
        return Err(error);
    }

    let result = Map::create(new_map, user_id, &mut conn).await?;
    for (layer_type, order_index) in LAYER_TYPES.iter().zip(0..) {
        let new_layer = LayerDto {
            id: Uuid::new_v4(),
            type_: *layer_type,
            name: format!("{layer_type} Layer"),
            is_alternative: false,
            map_id: result.id,
            order_index,
            marked_deleted: false,
        };
        let layer = Layer::create(result.id, new_layer, &mut conn).await?;

        // Immediately initialize a base layer image,
        // because the frontend would always have to create one
        // anyway.
        if layer.type_ == LayerType::Base {
            BaseLayerImages::create(
                BaseLayerImageDto {
                    id: Uuid::new_v4(),
                    layer_id: layer.id,
                    path: String::new(),
                    rotation: 0.0,
                    scale: 100.0,
                    x: 0,
                    y: 0,
                },
                &mut conn,
            )
            .await?;
        }
    }

    Ok(result)
}

/// Update a map in the database.
/// Checks if the map is owned by the requesting user.
///
/// # Errors
/// If the connection to the database could not be established.
/// If the requesting user is not the owner of the map.
pub async fn update(
    map_update: UpdateMapDto,
    id: i32,
    user_id: Uuid,
    pool: &SharedPool,
) -> Result<MapDto, ServiceError> {
    let mut conn = pool.get().await?;
    let map = Map::find_by_id(id, &mut conn).await?;
    if map.created_by != user_id {
        return Err(ServiceError {
            status_code: StatusCode::FORBIDDEN,
            reason: "no permission to update data".to_owned(),
        });
    }
    let result = Map::update(map_update, id, &mut conn).await?;
    Ok(result)
}

/// Update a maps geometry in the database.
/// Checks if the map is owned by the requesting user.
///
/// # Errors
/// * If the connection to the database could not be established.
/// * If the requesting user is not the owner of the map.
pub async fn update_geometry(
    map_update_geometry: UpdateMapGeometryDto,
    id: i32,
    user_id: Uuid,
    pool: &SharedPool,
) -> Result<MapDto, ServiceError> {
    let mut conn = pool.get().await?;
    let map = Map::find_by_id(id, &mut conn).await?;
    if map.created_by != user_id {
        return Err(ServiceError {
            status_code: StatusCode::FORBIDDEN,
            reason: "no permission to update geometry".to_owned(),
        });
    }

    let geometry_validation_result = is_valid_map_geometry(&map_update_geometry.geometry);
    if let Some(error) = geometry_validation_result {
        return Err(error);
    }

    let result = Map::update_geometry(map_update_geometry, id, &mut conn).await?;
    Ok(result)
}

/// Soft-deletes a map from the database.
/// Checks if the map is owned by the requesting user.
///
/// # Errors
/// * If the connection to the database could not be established.
/// * If the requesting user is not the owner of the map.
pub async fn delete_by_id(
    id: i32,
    user_id: Uuid,
    pool: &SharedPool,
) -> Result<MapDto, ServiceError> {
    let mut conn = pool.get().await?;

    let map = Map::find_by_id(id, &mut conn).await?;
    if map.created_by != user_id {
        return Err(ServiceError {
            status_code: StatusCode::FORBIDDEN,
            reason: "no permission to delete map".to_owned(),
        });
    }

    let result = Map::mark_for_deletion(id, &mut conn).await?;

    Ok(result)
}

/// Checks if a Polygon can be used as a maps geometry attribute.
fn is_valid_map_geometry(geometry: &Polygon<Point>) -> Option<ServiceError> {
    if geometry.rings.len() != 1 {
        return Some(ServiceError {
            status_code: StatusCode::BAD_REQUEST,
            reason: "Map geometry must have exactly one ring".to_owned(),
        });
    }

    let geometry_points_length = geometry.rings.get(0).unwrap_or(&Vec::new()).len();

    if geometry_points_length < 3 + 1 {
        return Some(ServiceError {
            status_code: StatusCode::BAD_REQUEST,
            reason: "Map geometry must be a polygon of at least three points.".to_owned(),
        });
    }

    None
}