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,
},
};
const LAYER_TYPES: [LayerType; 6] = [
LayerType::Base,
LayerType::Drawing,
LayerType::Soiltexture,
LayerType::Hydrology,
LayerType::Shade,
LayerType::Plants,
];
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)
}
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)
}
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?;
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)
}
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)
}
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)
}
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)
}
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
}