headless_lms_server/controllers/main_frontend/
exercise_repositories.rs

1use std::ops::Deref;
2
3use models::{
4    CourseOrExamId,
5    exercise_repositories::{ExerciseRepository, ExerciseRepositoryUpdate},
6};
7
8use crate::{domain, prelude::*};
9
10#[derive(Debug, Deserialize)]
11#[cfg_attr(feature = "ts_rs", derive(TS))]
12pub struct NewExerciseRepository {
13    course_id: Option<Uuid>,
14    exam_id: Option<Uuid>,
15    git_url: String,
16    public_key: Option<String>,
17    deploy_key: Option<String>,
18}
19
20/**
21POST `/api/v0/main-frontend/exercise-repositories/new
22*/
23
24#[instrument(skip(pool, file_store, app_conf))]
25async fn new(
26    pool: web::Data<PgPool>,
27    file_store: web::Data<dyn FileStore>,
28    repository: web::Json<NewExerciseRepository>,
29    user: AuthUser,
30    app_conf: web::Data<ApplicationConfiguration>,
31) -> ControllerResult<web::Json<Uuid>> {
32    let mut conn = pool.acquire().await?;
33    let course_or_exam_id = CourseOrExamId::from(repository.course_id, repository.exam_id)?;
34    let token = authorize(
35        &mut conn,
36        Act::Edit,
37        Some(user.id),
38        Res::from_course_or_exam_id(course_or_exam_id),
39    )
40    .await?;
41    // create pending repository
42    let new_repository_id = Uuid::new_v4();
43    models::exercise_repositories::new(
44        &mut conn,
45        new_repository_id,
46        course_or_exam_id,
47        &repository.git_url,
48        repository.public_key.as_deref(),
49        repository.deploy_key.as_deref(),
50    )
51    .await?;
52    // processing a repository may take a while, so this is done in the background
53    actix_web::rt::spawn(async move {
54        let file_store = file_store;
55        if let Err(err) = domain::exercise_repositories::process(
56            &mut conn,
57            new_repository_id,
58            &repository.git_url,
59            repository.public_key.as_deref(),
60            repository.deploy_key.as_deref(),
61            file_store.as_ref(),
62            app_conf.as_ref(),
63        )
64        .await
65        {
66            tracing::error!("Error while processing repository {new_repository_id}: {err}");
67        }
68    });
69    token.authorized_ok(web::Json(new_repository_id))
70}
71
72/**
73GET `/api/v0/main-frontend/exercise-repositories/course/:id`
74*/
75#[instrument(skip(pool))]
76async fn get_for_course(
77    pool: web::Data<PgPool>,
78    course_id: web::Path<Uuid>,
79    user: Option<AuthUser>,
80) -> ControllerResult<web::Json<Vec<ExerciseRepository>>> {
81    let mut conn = pool.acquire().await?;
82    let token = authorize(
83        &mut conn,
84        Act::View,
85        user.map(|u| u.id),
86        Res::Course(*course_id),
87    )
88    .await?;
89    let repos = models::exercise_repositories::get_for_course_or_exam(
90        &mut conn,
91        CourseOrExamId::Course(*course_id),
92    )
93    .await?;
94    token.authorized_ok(web::Json(repos))
95}
96
97/**
98GET `/api/v0/main-frontend/exercise-repositories/exam/:id`
99*/
100#[instrument(skip(pool))]
101async fn get_for_exam(
102    pool: web::Data<PgPool>,
103    exam_id: web::Path<Uuid>,
104    user: Option<AuthUser>,
105) -> ControllerResult<web::Json<Vec<ExerciseRepository>>> {
106    let mut conn = pool.acquire().await?;
107    let token = authorize(
108        &mut conn,
109        Act::View,
110        user.map(|u| u.id),
111        Res::Exam(*exam_id),
112    )
113    .await?;
114    let repos = models::exercise_repositories::get_for_course_or_exam(
115        &mut conn,
116        CourseOrExamId::Exam(*exam_id),
117    )
118    .await?;
119    token.authorized_ok(web::Json(repos))
120}
121
122/**
123DELETE `/api/v0/main-frontend/exercise-repositories/:id`
124*/
125#[instrument(skip(pool))]
126async fn delete(
127    pool: web::Data<PgPool>,
128    id: web::Path<Uuid>,
129    user: Option<AuthUser>,
130) -> ControllerResult<web::Json<bool>> {
131    let mut conn = pool.acquire().await?;
132    let repository = models::exercise_repositories::get(&mut conn, *id).await?;
133    let course_or_exam_id = CourseOrExamId::from(repository.course_id, repository.exam_id)?;
134    let token = authorize(
135        &mut conn,
136        Act::Edit,
137        user.map(|u| u.id),
138        Res::from_course_or_exam_id(course_or_exam_id),
139    )
140    .await?;
141
142    let mut tx = conn.begin().await?;
143    models::repository_exercises::delete_from_repository(&mut tx, *id).await?;
144    models::exercise_repositories::delete(&mut tx, *id).await?;
145    tx.commit().await?;
146
147    token.authorized_ok(web::Json(true))
148}
149
150/**
151PUT `/api/v0/main-frontend/exercise-repositories/:id`
152*/
153#[instrument(skip(pool))]
154async fn update(
155    pool: web::Data<PgPool>,
156    id: web::Path<Uuid>,
157    user: Option<AuthUser>,
158    update: web::Json<ExerciseRepositoryUpdate>,
159) -> ControllerResult<web::Json<bool>> {
160    let mut conn = pool.acquire().await?;
161    let repository = models::exercise_repositories::get(&mut conn, *id).await?;
162    let course_or_exam_id = CourseOrExamId::from(repository.course_id, repository.exam_id)?;
163    let token = authorize(
164        &mut conn,
165        Act::Edit,
166        user.map(|u| u.id),
167        Res::from_course_or_exam_id(course_or_exam_id),
168    )
169    .await?;
170
171    models::exercise_repositories::update(&mut conn, *id, update.deref()).await?;
172    token.authorized_ok(web::Json(true))
173}
174
175/**
176Add a route for each controller in this module.
177
178The name starts with an underline in order to appear before other functions in the module documentation.
179
180We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
181*/
182pub fn _add_routes(cfg: &mut ServiceConfig) {
183    cfg.route("/new", web::post().to(new))
184        .route("/course/{course_id}", web::get().to(get_for_course))
185        .route("/exam/{exam_id}", web::get().to(get_for_exam))
186        .route("/{id}", web::delete().to(delete))
187        .route("/{id}", web::put().to(update));
188}