headless_lms_server/controllers/main_frontend/
exercise_services.rs

1//! Controllers for requests starting with `/api/v0/main-frontend/exercise-services/`.
2
3use headless_lms_models::{ModelError, exercise_service_info::fetch_and_upsert_service_info};
4use models::exercise_services::{ExerciseService, ExerciseServiceNewOrUpdate};
5use utoipa::{OpenApi, ToSchema};
6
7use crate::{domain::models_requests, prelude::*};
8
9/**
10DELETE `/api/v0/main-frontend/exercise-services/:id`
11*/
12#[derive(OpenApi)]
13#[openapi(paths(
14    delete_exercise_service,
15    add_exercise_service,
16    get_exercise_service_by_id,
17    get_exercise_services,
18    update_exercise_service
19))]
20pub(crate) struct MainFrontendExerciseServicesApiDoc;
21
22#[utoipa::path(
23    delete,
24    path = "/{exercise_service_id}",
25    operation_id = "deleteExerciseService",
26    tag = "exercise_services",
27    params(
28        ("exercise_service_id" = Uuid, Path, description = "Exercise service id")
29    ),
30    responses(
31        (status = 200, description = "Deleted exercise service", body = ExerciseService)
32    )
33)]
34#[instrument(skip(pool))]
35async fn delete_exercise_service(
36    exercise_service_id: web::Path<Uuid>,
37    pool: web::Data<PgPool>,
38    user: AuthUser,
39) -> ControllerResult<web::Json<ExerciseService>> {
40    let mut conn = pool.acquire().await?;
41    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::ExerciseService).await?;
42
43    let deleted =
44        models::exercise_services::delete_exercise_service(&mut conn, *exercise_service_id).await?;
45
46    token.authorized_ok(web::Json(deleted))
47}
48
49#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
50
51pub struct ExerciseServiceWithError {
52    exercise_service: ExerciseService,
53    service_info_error: Option<String>,
54}
55
56/**
57POST `/api/v0/main-frontend/exercise-services`
58*/
59#[utoipa::path(
60    post,
61    path = "/",
62    operation_id = "createExerciseService",
63    tag = "exercise_services",
64    request_body = ExerciseServiceNewOrUpdate,
65    responses(
66        (status = 200, description = "Created exercise service", body = ExerciseServiceWithError)
67    )
68)]
69#[instrument(skip(pool))]
70async fn add_exercise_service(
71    pool: web::Data<PgPool>,
72    user: AuthUser,
73    payload: web::Json<ExerciseServiceNewOrUpdate>,
74) -> ControllerResult<web::Json<ExerciseServiceWithError>> {
75    let mut conn = pool.acquire().await?;
76    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::ExerciseService).await?;
77
78    let exercise_service = payload.0;
79    let exercise_service =
80        models::exercise_services::insert_exercise_service(&mut conn, &exercise_service).await?;
81
82    let exercise_service_with_error =
83        try_to_get_exercise_service_info(&mut conn, exercise_service).await?;
84    token.authorized_ok(web::Json(exercise_service_with_error))
85}
86
87/**
88GET `/api/v0/main-frontend/exercise-services/:id`
89*/
90#[utoipa::path(
91    get,
92    path = "/{exercise_service_id}",
93    operation_id = "getExerciseServiceById",
94    tag = "exercise_services",
95    params(
96        ("exercise_service_id" = Uuid, Path, description = "Exercise service id")
97    ),
98    responses(
99        (status = 200, description = "Exercise service", body = ExerciseService)
100    )
101)]
102#[instrument(skip(pool))]
103async fn get_exercise_service_by_id(
104    exercise_service_id: web::Path<Uuid>,
105    pool: web::Data<PgPool>,
106    user: AuthUser,
107) -> ControllerResult<web::Json<ExerciseService>> {
108    let mut conn = pool.acquire().await?;
109    let exercise_service =
110        models::exercise_services::get_exercise_service(&mut conn, *exercise_service_id).await?;
111
112    let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::ExerciseService).await?;
113    token.authorized_ok(web::Json(exercise_service))
114}
115
116/**
117GET `/api/v0/main-frontend/exercise-services`
118*/
119#[utoipa::path(
120    get,
121    path = "/",
122    operation_id = "getExerciseServices",
123    tag = "exercise_services",
124    responses(
125        (status = 200, description = "Exercise services", body = Vec<ExerciseService>)
126    )
127)]
128#[instrument(skip(pool))]
129async fn get_exercise_services(
130    pool: web::Data<PgPool>,
131    user: AuthUser,
132) -> ControllerResult<web::Json<Vec<ExerciseService>>> {
133    let mut conn = pool.acquire().await?;
134    let exercise_services = models::exercise_services::get_exercise_services(&mut conn).await?;
135
136    let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::ExerciseService).await?;
137    token.authorized_ok(web::Json(exercise_services))
138}
139
140/**
141PUT `/api/v0/main-frontend/exercise-services/:id`
142*/
143#[utoipa::path(
144    put,
145    path = "/{exercise_service_id}",
146    operation_id = "updateExerciseService",
147    tag = "exercise_services",
148    params(
149        ("exercise_service_id" = Uuid, Path, description = "Exercise service id")
150    ),
151    request_body = ExerciseServiceNewOrUpdate,
152    responses(
153        (status = 200, description = "Updated exercise service", body = ExerciseServiceWithError)
154    )
155)]
156#[instrument(skip(pool))]
157async fn update_exercise_service(
158    payload: web::Json<ExerciseServiceNewOrUpdate>,
159    exercise_service_id: web::Path<Uuid>,
160    pool: web::Data<PgPool>,
161    user: AuthUser,
162) -> ControllerResult<web::Json<ExerciseServiceWithError>> {
163    let mut conn = pool.acquire().await?;
164    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::ExerciseService).await?;
165
166    let updated_exercise_service = payload.0;
167    let updated_service = models::exercise_services::update_exercise_service(
168        &mut conn,
169        *exercise_service_id,
170        &updated_exercise_service,
171    )
172    .await?;
173
174    let exercise_service_with_error =
175        try_to_get_exercise_service_info(&mut conn, updated_service).await?;
176    token.authorized_ok(web::Json(exercise_service_with_error))
177}
178
179async fn try_to_get_exercise_service_info(
180    conn: &mut PgConnection,
181    exercise_service: ExerciseService,
182) -> Result<ExerciseServiceWithError, ModelError> {
183    // try to get exercise service info
184    let service_info_error = if let Err(err) = fetch_and_upsert_service_info(
185        conn,
186        &exercise_service,
187        models_requests::fetch_service_info_fast,
188    )
189    .await
190    {
191        tracing::warn!(
192            "Failed to get exercise service info for new exercise service {}: {err}",
193            exercise_service.name
194        );
195        Some(err.to_string())
196    } else {
197        None
198    };
199
200    let with_error = ExerciseServiceWithError {
201        exercise_service,
202        service_info_error,
203    };
204    Ok(with_error)
205}
206
207/**
208Add a route for each controller in this module.
209
210The name starts with an underline in order to appear before other functions in the module documentation.
211
212We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
213*/
214pub fn _add_routes(cfg: &mut ServiceConfig) {
215    cfg.route("/", web::post().to(add_exercise_service))
216        .route("/", web::get().to(get_exercise_services))
217        .route(
218            "/{exercise_service_id}",
219            web::delete().to(delete_exercise_service),
220        )
221        .route(
222            "/{exercise_service_id}",
223            web::put().to(update_exercise_service),
224        )
225        .route(
226            "/{exercise_service_id}",
227            web::get().to(get_exercise_service_by_id),
228        );
229}