From 7cb03325bda7812c62f09f0712262cf3224d0e4e Mon Sep 17 00:00:00 2001 From: Eloi Charpentier Date: Tue, 19 Nov 2024 15:49:43 +0100 Subject: [PATCH] editoast: work-schedules: add endpoints for easier edition and viewing Signed-off-by: Eloi Charpentier --- editoast/openapi.yaml | 108 +++++++++++++ editoast/src/models/work_schedules.rs | 2 +- editoast/src/views/work_schedules.rs | 221 +++++++++++++++++++++++--- 3 files changed, 310 insertions(+), 21 deletions(-) diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index ebbc3507d24..26dca1062ad 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -3059,6 +3059,89 @@ paths: application/json: schema: $ref: '#/components/schemas/WorkScheduleCreateResponse' + /work_schedules/group: + get: + tags: + - work_schedules + responses: + '201': + description: The existing work schedule group ids + content: + application/json: + schema: + type: array + items: + type: integer + format: int64 + post: + tags: + - work_schedules + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/WorkScheduleGroupCreateForm' + required: true + responses: + '201': + description: The id of the created work schedule group + content: + application/json: + schema: + $ref: '#/components/schemas/WorkScheduleGroupCreateResponse' + /work_schedules/group/{id}: + get: + tags: + - work_schedules + parameters: + - name: id + in: path + description: A work schedule group ID + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: The work schedules in the group + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/WorkScheduleResponse' + '404': + description: Work schedule group not found + put: + tags: + - work_schedules + parameters: + - name: id + in: path + description: A work schedule group ID + required: true + schema: + type: integer + format: int64 + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/WorkScheduleItemForm' + required: true + responses: + '201': + description: The work schedules have been created + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/WorkSchedule' + '404': + description: Work schedule group not found /work_schedules/project_path: post: tags: @@ -4280,6 +4363,7 @@ components: - $ref: '#/components/schemas/EditoastTrainScheduleErrorInfraNotFound' - $ref: '#/components/schemas/EditoastTrainScheduleErrorNotFound' - $ref: '#/components/schemas/EditoastWorkScheduleErrorNameAlreadyUsed' + - $ref: '#/components/schemas/EditoastWorkScheduleErrorWorkScheduleGroupNotFound' description: Generated error type for Editoast discriminator: propertyName: type @@ -5698,6 +5782,30 @@ components: type: string enum: - editoast:work_schedule:NameAlreadyUsed + EditoastWorkScheduleErrorWorkScheduleGroupNotFound: + type: object + required: + - type + - status + - message + properties: + context: + type: object + required: + - id + properties: + id: + type: integer + message: + type: string + status: + type: integer + enum: + - 404 + type: + type: string + enum: + - editoast:work_schedule:WorkScheduleGroupNotFound EffortCurve: type: object required: diff --git a/editoast/src/models/work_schedules.rs b/editoast/src/models/work_schedules.rs index 80da485fc51..154849c30d5 100644 --- a/editoast/src/models/work_schedules.rs +++ b/editoast/src/models/work_schedules.rs @@ -29,7 +29,7 @@ pub enum WorkScheduleType { Track, } -#[derive(Debug, Default, Clone, Model)] +#[derive(Debug, Default, Clone, Model, Serialize, Deserialize, ToSchema)] #[model(table = editoast_models::tables::work_schedule)] #[model(gen(batch_ops = c, list))] pub struct WorkSchedule { diff --git a/editoast/src/views/work_schedules.rs b/editoast/src/views/work_schedules.rs index 0f7f116daf2..9766bbec423 100644 --- a/editoast/src/views/work_schedules.rs +++ b/editoast/src/views/work_schedules.rs @@ -1,5 +1,5 @@ -use axum::extract::Json; use axum::extract::State; +use axum::extract::{Json, Path}; use axum::Extension; use chrono::DateTime; use chrono::Utc; @@ -12,7 +12,7 @@ use serde::Deserialize; use serde::Serialize; use std::result::Result as StdResult; use thiserror::Error; -use utoipa::ToSchema; +use utoipa::{IntoParams, ToSchema}; use crate::core::pathfinding::TrackRange as CoreTrackRange; use crate::error::InternalError; @@ -32,6 +32,14 @@ crate::routes! { "/work_schedules" => { create, "/project_path" => project_path, + "/group" => { + create_group, + list_groups, + "/{id}" => { + get_group, + put_in_group, + }, + }, }, } @@ -41,12 +49,21 @@ editoast_common::schemas! { WorkScheduleItemForm, } +#[derive(IntoParams, Deserialize)] +struct WorkScheduleGroupIdParam { + /// A work schedule group ID + id: i64, +} + #[derive(Debug, Error, EditoastError)] #[editoast_error(base_id = "work_schedule")] enum WorkScheduleError { #[error("Name '{name}' already used")] #[editoast_error(status = 400)] NameAlreadyUsed { name: String }, + #[error("Work schedule group '{id}' not found")] + #[editoast_error(status = 404)] + WorkScheduleGroupNotFound { id: i64 }, } pub fn map_diesel_error(e: InternalError, name: impl AsRef) -> InternalError { @@ -149,36 +166,31 @@ async fn create( work_schedules, }): Json, ) -> Result> { - let authorized = auth - .check_roles([BuiltinRole::WorkScheduleWrite].into()) - .await - .map_err(AuthorizationError::AuthError)?; - if !authorized { - return Err(AuthorizationError::Unauthorized.into()); - } + // Create the group (using the method for the create group endpoint) + let work_schedule_group = create_group( + State(app_state.clone()), + Extension(auth), + Json(WorkScheduleGroupCreateForm { + work_schedule_group_name, + }), + ) + .await?; let db_pool = app_state.db_pool_v2.clone(); let conn = &mut db_pool.get().await?; - // Create the work_schedule_group - let work_schedule_group = WorkScheduleGroup::changeset() - .name(work_schedule_group_name.clone()) - .creation_date(Utc::now()) - .create(conn) - .await; - let work_schedule_group = - work_schedule_group.map_err(|e| map_diesel_error(e, work_schedule_group_name))?; - // Create work schedules let work_schedules_changesets = work_schedules .into_iter() - .map(|work_schedule| work_schedule.into_work_schedule_changeset(work_schedule_group.id)) + .map(|work_schedule| { + work_schedule.into_work_schedule_changeset(work_schedule_group.work_schedule_group_id) + }) .collect::>(); let _work_schedules: Vec<_> = WorkSchedule::create_batch(conn, work_schedules_changesets).await?; Ok(Json(WorkScheduleCreateResponse { - work_schedule_group_id: work_schedule_group.id, + work_schedule_group_id: work_schedule_group.work_schedule_group_id, })) } @@ -275,6 +287,175 @@ async fn project_path( Ok(Json(projections)) } +#[derive(Serialize, Deserialize, ToSchema)] +struct WorkScheduleGroupCreateForm { + work_schedule_group_name: String, +} + +#[derive(Serialize, Deserialize, ToSchema)] +struct WorkScheduleGroupCreateResponse { + work_schedule_group_id: i64, +} + +#[utoipa::path( + post, path = "", + tag = "work_schedules", + request_body = WorkScheduleGroupCreateForm, + responses( + (status = 201, body = WorkScheduleGroupCreateResponse, description = "The id of the created work schedule group"), + ) +)] +async fn create_group( + State(app_state): State, + Extension(auth): AuthenticationExt, + Json(WorkScheduleGroupCreateForm { + work_schedule_group_name, + }): Json, +) -> Result> { + let authorized = auth + .check_roles([BuiltinRole::WorkScheduleWrite].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Unauthorized.into()); + } + + let db_pool = app_state.db_pool_v2.clone(); + let conn = &mut db_pool.get().await?; + + // Create the work_schedule_group + let work_schedule_group = WorkScheduleGroup::changeset() + .name(work_schedule_group_name.clone()) + .creation_date(Utc::now()) + .create(conn) + .await; + let work_schedule_group = + work_schedule_group.map_err(|e| map_diesel_error(e, work_schedule_group_name))?; + Ok(Json(WorkScheduleGroupCreateResponse { + work_schedule_group_id: work_schedule_group.id, + })) +} + +#[utoipa::path( + get, path = "", + tag = "work_schedules", + responses( + (status = 201, body = Vec, description = "The existing work schedule group ids"), + ) +)] +async fn list_groups( + State(app_state): State, + Extension(auth): AuthenticationExt, +) -> Result>> { + let authorized = auth + .check_roles([BuiltinRole::WorkScheduleRead].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Unauthorized.into()); + } + + let db_pool = app_state.db_pool_v2.clone(); + let conn = &mut db_pool.get().await?; + + let selection_setting = SelectionSettings::new(); + let work_schedule_group_ids = WorkScheduleGroup::list(conn, selection_setting) + .await? + .iter() + .map(|group| group.id) + .collect::>(); + + Ok(Json(work_schedule_group_ids)) +} + +#[derive(Serialize, Deserialize, ToSchema)] +struct WorkScheduleInGroupCreateForm { + work_schedules: Vec, +} + +#[utoipa::path( + put, path = "", + tag = "work_schedules", + request_body = Vec, + params(WorkScheduleGroupIdParam), + responses( + (status = 201, description = "The work schedules have been created", body = Vec), + (status = 404, description = "Work schedule group not found"), + ) +)] +async fn put_in_group( + State(app_state): State, + Extension(auth): AuthenticationExt, + raw_group_id: Path, + Json(work_schedules): Json>, +) -> Result>> { + let authorized = auth + .check_roles([BuiltinRole::WorkScheduleWrite].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Unauthorized.into()); + } + let group_id = raw_group_id.id; + + let db_pool = app_state.db_pool_v2.clone(); + let conn = &mut db_pool.get().await?; + + // Check that the group exists + WorkScheduleGroup::retrieve_or_fail(conn, group_id, || { + WorkScheduleError::WorkScheduleGroupNotFound { id: group_id } + }) + .await?; + + // Create work schedules + let work_schedules_changesets = work_schedules + .into_iter() + .map(|work_schedule| work_schedule.into_work_schedule_changeset(group_id)) + .collect::>(); + let work_schedules = WorkSchedule::create_batch(conn, work_schedules_changesets).await?; + + Ok(Json(work_schedules)) +} + +#[utoipa::path( + get, path = "", + tag = "work_schedules", + params(WorkScheduleGroupIdParam), + responses( + (status = 200, description = "The work schedules in the group", body = Vec), + (status = 404, description = "Work schedule group not found"), + ) +)] +async fn get_group( + State(app_state): State, + Extension(auth): AuthenticationExt, + raw_group_id: Path, +) -> Result>> { + let authorized = auth + .check_roles([BuiltinRole::WorkScheduleRead].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Unauthorized.into()); + } + let group_id = raw_group_id.id; + + let db_pool = app_state.db_pool_v2.clone(); + let conn = &mut db_pool.get().await?; + + // Check that the group exists + WorkScheduleGroup::retrieve_or_fail(conn, group_id, || { + WorkScheduleError::WorkScheduleGroupNotFound { id: group_id } + }) + .await?; + + let selection_setting = + SelectionSettings::new().filter(move || WorkSchedule::WORK_SCHEDULE_GROUP_ID.eq(group_id)); + let work_schedules = WorkSchedule::list(conn, selection_setting).await?; + + Ok(Json(work_schedules)) +} + #[cfg(test)] pub mod tests { use axum::http::StatusCode;