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