Skip to content

Commit

Permalink
editoast: update paced train endpont
Browse files Browse the repository at this point in the history
Signed-off-by: Egor Berezovskiy <[email protected]>
  • Loading branch information
Wadjetz committed Mar 3, 2025
1 parent afe570c commit 28812cc
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 68 deletions.
2 changes: 1 addition & 1 deletion editoast/editoast_schemas/src/primitives/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub enum PositiveDurationError {
/// let err_s = r#"{"duration":"P1M"}"#; // 1 month
/// assert!(serde_json::from_str::<MyStruct>(err_s).is_err());
/// ```
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct PositiveDuration(ChronoDuration);

impl TryFrom<ChronoDuration> for PositiveDuration {
Expand Down
70 changes: 70 additions & 0 deletions editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1539,6 +1539,51 @@ paths:
responses:
'204':
description: All paced_trains have been deleted
/paced_train/{paced_train_id}:
put:
tags:
- timetable
- paced_train
summary: Update a train schedule and its result
parameters:
- name: paced_train_id
in: path
required: true
schema:
type: integer
format: int64
requestBody:
content:
application/json:
schema:
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:
'204':
description: Paced train have been updated
content:
application/json:
schema:
$ref: '#/components/schemas/PacedTrainResult'
/projects:
get:
tags:
Expand Down Expand Up @@ -4910,6 +4955,7 @@ components:
- $ref: '#/components/schemas/EditoastOperationErrorObjectNotFound'
- $ref: '#/components/schemas/EditoastPacedTrainErrorBatchPacedTrainNotFound'
- $ref: '#/components/schemas/EditoastPacedTrainErrorDatabase'
- $ref: '#/components/schemas/EditoastPacedTrainErrorPacedTrainNotFound'
- $ref: '#/components/schemas/EditoastPaginationErrorInvalidPage'
- $ref: '#/components/schemas/EditoastPaginationErrorInvalidPageSize'
- $ref: '#/components/schemas/EditoastPathfindingErrorInfraNotFound'
Expand Down Expand Up @@ -5561,6 +5607,30 @@ components:
type: string
enum:
- editoast:paced_train:Database
EditoastPacedTrainErrorPacedTrainNotFound:
type: object
required:
- type
- status
- message
properties:
context:
type: object
required:
- paced_train_id
properties:
paced_train_id:
type: integer
message:
type: string
status:
type: integer
enum:
- 400
type:
type: string
enum:
- editoast:paced_train:PacedTrainNotFound
EditoastPaginationErrorInvalidPage:
type: object
required:
Expand Down
86 changes: 82 additions & 4 deletions editoast/src/views/paced_train.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::error::Result;
use crate::models::prelude::*;
use crate::views::ListId;
use axum::extract::Json;
use axum::extract::Path;
use axum::extract::State;
use axum::{response::IntoResponse, Extension};
use editoast_authz::BuiltinRole;
Expand All @@ -10,14 +8,23 @@ use editoast_models::DbConnectionPoolV2;
use editoast_schemas::paced_train::PacedTrainBase;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use utoipa::IntoParams;
use utoipa::ToSchema;

use super::AuthenticationExt;
use crate::{models::paced_train::PacedTrain, views::AuthorizationError};
use crate::error::Result;
use crate::models::paced_train::PacedTrain;
use crate::models::paced_train::PacedTrainChangeset;
use crate::models::prelude::*;
use crate::views::AuthorizationError;
use crate::views::ListId;

crate::routes! {
"/paced_train" => {
delete,
"/{paced_train_id}" => {
update_paced_train,
},
},
}

Expand All @@ -32,6 +39,8 @@ enum PacedTrainError {
#[error("{count} paced train(s) could not be found")]
#[editoast_error(status = 404)]
BatchPacedTrainNotFound { count: usize },
#[error("Paced train {paced_train_id} does not exist")]
PacedTrainNotFound { paced_train_id: i64 },
#[error(transparent)]
#[editoast_error(status = 500)]
Database(#[from] editoast_models::model::Error),
Expand All @@ -55,6 +64,48 @@ impl From<PacedTrain> for PacedTrainResult {
}
}

#[derive(Debug, IntoParams, Deserialize)]
struct PacedTrainIdParam {
paced_train_id: i64,
}

/// Update a train schedule and its result
#[utoipa::path(
put, path = "",
tag = "timetable,paced_train",
params(PacedTrainIdParam),
request_body = inline(PacedTrainBase),
responses(
(status = 204, body = PacedTrainResult, description = "Paced train have been updated")
)
)]
async fn update_paced_train(
State(db_pool): State<DbConnectionPoolV2>,
Extension(auth): AuthenticationExt,
Path(PacedTrainIdParam { paced_train_id }): Path<PacedTrainIdParam>,
Json(paced_train_base): Json<PacedTrainBase>,
) -> Result<impl IntoResponse> {
let authorized = auth
.check_roles([BuiltinRole::InfraRead, BuiltinRole::TimetableWrite].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 = "",
Expand Down Expand Up @@ -90,6 +141,7 @@ async fn delete(

#[cfg(test)]
mod tests {
use chrono::Duration;
use rstest::rstest;
use serde_json::json;

Expand Down Expand Up @@ -121,6 +173,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();
Expand Down
3 changes: 2 additions & 1 deletion front/public/locales/en/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@
},
"paced_train": {
"Database": "Internal error (database)",
"BatchPacedTrainNotFound": "Some Paced trains could not be found"
"BatchPacedTrainNotFound": "Some Paced trains could not be found",
"PacedTrainNotFound": "Paced train '{{paced_train_id}}' not found"
},
"url": {
"InvalidUrl": "Invalid url '{{url}}'"
Expand Down
3 changes: 2 additions & 1 deletion front/public/locales/fr/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@
},
"paced_train": {
"Database": "Erreur interne (base de données)",
"BatchPacedTrainNotFound": "Certaines missions sont introuvables"
"BatchPacedTrainNotFound": "Certaines missions sont introuvables",
"PacedTrainNotFound": "Mission '{{paced_train_id}}' non trouvée"
},
"url": {
"InvalidUrl": "Url invalide '{{url}}'"
Expand Down
Loading

0 comments on commit 28812cc

Please sign in to comment.