diff --git a/editoast/editoast_schemas/src/primitives/duration.rs b/editoast/editoast_schemas/src/primitives/duration.rs index 52aad6cee2e..39886e63b57 100644 --- a/editoast/editoast_schemas/src/primitives/duration.rs +++ b/editoast/editoast_schemas/src/primitives/duration.rs @@ -57,7 +57,7 @@ pub enum PositiveDurationError { /// let err_s = r#"{"duration":"P1M"}"#; // 1 month /// assert!(serde_json::from_str::(err_s).is_err()); /// ``` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct PositiveDuration(ChronoDuration); impl TryFrom for PositiveDuration { diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index e6eb16f1d91..9e221bcf5f9 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -1626,6 +1626,7 @@ paths: tags: - timetable - paced_train + summary: Update a paced train parameters: - name: id in: path @@ -1637,7 +1638,26 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PacedTrainForm' + allOf: + - $ref: '#/components/schemas/TrainScheduleBase' + - type: object + required: + - paced + properties: + paced: + type: object + required: + - duration + - step + properties: + duration: + type: string + description: Duration of the paced train, an ISO 8601 format is expected + example: PT2H + step: + type: string + description: Time between two occurrences, an ISO 8601 format is expected + example: PT15M required: true responses: '200': diff --git a/editoast/src/views/paced_train.rs b/editoast/src/views/paced_train.rs index 9bc670b776e..a1cb8c5fbc6 100644 --- a/editoast/src/views/paced_train.rs +++ b/editoast/src/views/paced_train.rs @@ -26,6 +26,7 @@ use super::SimulationSummaryResult; use crate::core::simulation::SimulationResponse; use crate::error::Result; use crate::models::paced_train::PacedTrain; +use crate::models::paced_train::PacedTrainChangeset; use crate::models::prelude::*; use crate::views::projection::ProjectPathTrainResult; use crate::views::AuthorizationError; @@ -130,6 +131,43 @@ async fn get_by_id( Ok(Json(paced_train)) } +/// Update a paced train +#[utoipa::path( + put, path = "", + tag = "timetable,paced_train", + params(PacedTrainIdParam), + request_body = inline(PacedTrainBase), + responses( + (status = 200, body = PacedTrainResult, description = "Paced train have been updated") + ) +)] +async fn update_paced_train( + State(db_pool): State, + Extension(auth): AuthenticationExt, + Path(PacedTrainIdParam { id: paced_train_id }): Path, + Json(paced_train_base): Json, +) -> Result { + let authorized = auth + .check_roles([BuiltinRole::OperationalStudies, BuiltinRole::Stdcm].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Forbidden.into()); + } + + let conn = &mut db_pool.get().await?; + let paced_train_changeset: PacedTrainChangeset = paced_train_base.into(); + let paced_train = paced_train_changeset + .update_or_fail(conn, paced_train_id, || { + PacedTrainError::PacedTrainNotFound { paced_train_id } + }) + .await?; + + let paced_train_result: PacedTrainResult = paced_train.into(); + + Ok(Json(paced_train_result)) +} + /// Delete a paced train #[utoipa::path( delete, path = "", @@ -163,26 +201,6 @@ async fn delete( Ok(axum::http::StatusCode::NO_CONTENT) } -#[utoipa::path( - put, path = "", - tag = "timetable,paced_train", - request_body = PacedTrainForm, - params(PacedTrainIdParam), - responses( - (status = 200, description = "Paced train have been updated", body = PacedTrainResult) - ) -)] -async fn update_paced_train( - State(_db_pool): State, - Extension(_auth): AuthenticationExt, - Path(PacedTrainIdParam { - id: _paced_train_id, - }): Path, - Json(_paced_train_form): Json, -) -> Result> { - todo!(); -} - #[derive(Debug, Clone, Deserialize, ToSchema)] struct SimulationBatchForm { infra_id: i64, @@ -316,6 +334,7 @@ async fn project_path( #[cfg(test)] mod tests { use axum::http::StatusCode; + use chrono::Duration; use pretty_assertions::assert_eq; use rstest::rstest; use serde_json::json; @@ -346,6 +365,32 @@ mod tests { assert_eq!(response.len(), 1); } + #[rstest] + async fn update_paced_train() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let timetable = create_timetable(&mut pool.get_ok()).await; + let paced_train = create_simple_paced_train(&mut pool.get_ok(), timetable.id).await; + + let mut paced_train_base = simple_paced_train_base(); + paced_train_base.paced.duration = Duration::minutes(90).try_into().unwrap(); + paced_train_base.paced.step = Duration::minutes(15).try_into().unwrap(); + + let request = app + .put(format!("/paced_train/{}", paced_train.id).as_str()) + .json(&json!(&paced_train_base)); + + let response: PacedTrainResult = + app.fetch(request).assert_status(StatusCode::OK).json_into(); + + assert_eq!( + response.paced_train.paced.duration, + paced_train_base.paced.duration + ); + assert_eq!(response.paced_train.paced.step, paced_train_base.paced.step); + } + #[rstest] async fn paced_train_delete() { let app = TestAppBuilder::default_app(); diff --git a/front/src/common/api/generatedEditoastApi.ts b/front/src/common/api/generatedEditoastApi.ts index ab8bba6e601..b0873110c0b 100644 --- a/front/src/common/api/generatedEditoastApi.ts +++ b/front/src/common/api/generatedEditoastApi.ts @@ -512,7 +512,7 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/paced_train/${queryArg.id}`, method: 'PUT', - body: queryArg.pacedTrainForm, + body: queryArg.body, }), invalidatesTags: ['timetable', 'paced_train'], }), @@ -1604,7 +1604,14 @@ export type PutPacedTrainByIdApiResponse = /** status 200 Paced train have been updated */ PacedTrainResult; export type PutPacedTrainByIdApiArg = { id: number; - pacedTrainForm: PacedTrainForm; + body: TrainScheduleBase & { + paced: { + /** Duration of the paced train, an ISO 8601 format is expected */ + duration: string; + /** Time between two occurrences, an ISO 8601 format is expected */ + step: string; + }; + }; }; export type GetPacedTrainByIdPathApiResponse = /** status 200 The path */ PathfindingResult; export type GetPacedTrainByIdPathApiArg = { @@ -3249,10 +3256,6 @@ export type PacedTrainResult = PacedTrainBase & { id: number; timetable_id: number; }; -export type PacedTrainForm = PacedTrainBase & { - /** Timetable attached to the train schedule */ - timetable_id?: number | null; -}; export type ReportTrain = { /** Total energy consumption */ energy_consumption: number;