headless_lms_server/controllers/main_frontend/
exercise_repositories.rs1use std::ops::Deref;
2
3use models::{
4 CourseOrExamId,
5 exercise_repositories::{ExerciseRepository, ExerciseRepositoryUpdate},
6};
7use utoipa::{OpenApi, ToSchema};
8
9use crate::{domain, prelude::*};
10
11#[derive(OpenApi)]
12#[openapi(paths(new, get_for_course, get_for_exam, delete, update))]
13pub(crate) struct MainFrontendExerciseRepositoriesApiDoc;
14
15#[derive(Debug, Deserialize, ToSchema)]
16
17pub struct NewExerciseRepository {
18 course_id: Option<Uuid>,
19 exam_id: Option<Uuid>,
20 git_url: String,
21 public_key: Option<String>,
22 deploy_key: Option<String>,
23}
24
25#[utoipa::path(
29 post,
30 path = "/new",
31 operation_id = "createExerciseRepository",
32 tag = "exercise_repositories",
33 request_body = NewExerciseRepository,
34 responses(
35 (status = 200, description = "Created exercise repository id", body = Uuid)
36 )
37)]
38#[instrument(skip(pool, file_store, app_conf))]
39async fn new(
40 pool: web::Data<PgPool>,
41 file_store: web::Data<dyn FileStore>,
42 repository: web::Json<NewExerciseRepository>,
43 user: AuthUser,
44 app_conf: web::Data<ApplicationConfiguration>,
45) -> ControllerResult<web::Json<Uuid>> {
46 let mut conn = pool.acquire().await?;
47 let course_or_exam_id =
48 CourseOrExamId::from_course_and_exam_ids(repository.course_id, repository.exam_id)?;
49 let token = authorize(
50 &mut conn,
51 Act::Edit,
52 Some(user.id),
53 Res::from_course_or_exam_id(course_or_exam_id),
54 )
55 .await?;
56 let new_repository_id = Uuid::new_v4();
58 models::exercise_repositories::new(
59 &mut conn,
60 new_repository_id,
61 course_or_exam_id,
62 &repository.git_url,
63 repository.public_key.as_deref(),
64 repository.deploy_key.as_deref(),
65 )
66 .await?;
67 actix_web::rt::spawn(async move {
69 let file_store = file_store;
70 if let Err(err) = domain::exercise_repositories::process(
71 &mut conn,
72 new_repository_id,
73 &repository.git_url,
74 repository.public_key.as_deref(),
75 repository.deploy_key.as_deref(),
76 file_store.as_ref(),
77 app_conf.as_ref(),
78 )
79 .await
80 {
81 tracing::error!("Error while processing repository {new_repository_id}: {err}");
82 }
83 });
84 token.authorized_ok(web::Json(new_repository_id))
85}
86
87#[utoipa::path(
91 get,
92 path = "/course/{course_id}",
93 operation_id = "getExerciseRepositoriesForCourse",
94 tag = "exercise_repositories",
95 params(
96 ("course_id" = Uuid, Path, description = "Course id")
97 ),
98 responses(
99 (status = 200, description = "Exercise repositories for course", body = Vec<ExerciseRepository>)
100 )
101)]
102#[instrument(skip(pool))]
103async fn get_for_course(
104 pool: web::Data<PgPool>,
105 course_id: web::Path<Uuid>,
106 user: AuthUser,
107) -> ControllerResult<web::Json<Vec<ExerciseRepository>>> {
108 let mut conn = pool.acquire().await?;
109 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
110 let repos = models::exercise_repositories::get_for_course_or_exam(
111 &mut conn,
112 CourseOrExamId::Course(*course_id),
113 )
114 .await?;
115 token.authorized_ok(web::Json(repos))
116}
117
118#[utoipa::path(
122 get,
123 path = "/exam/{exam_id}",
124 operation_id = "getExerciseRepositoriesForExam",
125 tag = "exercise_repositories",
126 params(
127 ("exam_id" = Uuid, Path, description = "Exam id")
128 ),
129 responses(
130 (status = 200, description = "Exercise repositories for exam", body = Vec<ExerciseRepository>)
131 )
132)]
133#[instrument(skip(pool))]
134async fn get_for_exam(
135 pool: web::Data<PgPool>,
136 exam_id: web::Path<Uuid>,
137 user: AuthUser,
138) -> ControllerResult<web::Json<Vec<ExerciseRepository>>> {
139 let mut conn = pool.acquire().await?;
140 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Exam(*exam_id)).await?;
141 let repos = models::exercise_repositories::get_for_course_or_exam(
142 &mut conn,
143 CourseOrExamId::Exam(*exam_id),
144 )
145 .await?;
146 token.authorized_ok(web::Json(repos))
147}
148
149#[utoipa::path(
153 delete,
154 path = "/{id}",
155 operation_id = "deleteExerciseRepository",
156 tag = "exercise_repositories",
157 params(
158 ("id" = Uuid, Path, description = "Exercise repository id")
159 ),
160 responses(
161 (status = 200, description = "Deleted exercise repository", body = bool)
162 )
163)]
164#[instrument(skip(pool))]
165async fn delete(
166 pool: web::Data<PgPool>,
167 id: web::Path<Uuid>,
168 user: Option<AuthUser>,
169) -> ControllerResult<web::Json<bool>> {
170 let mut conn = pool.acquire().await?;
171 let repository = models::exercise_repositories::get(&mut conn, *id).await?;
172 let course_or_exam_id =
173 CourseOrExamId::from_course_and_exam_ids(repository.course_id, repository.exam_id)?;
174 let token = authorize(
175 &mut conn,
176 Act::Edit,
177 user.map(|u| u.id),
178 Res::from_course_or_exam_id(course_or_exam_id),
179 )
180 .await?;
181
182 let mut tx = conn.begin().await?;
183 models::repository_exercises::delete_from_repository(&mut tx, *id).await?;
184 models::exercise_repositories::delete(&mut tx, *id).await?;
185 tx.commit().await?;
186
187 token.authorized_ok(web::Json(true))
188}
189
190#[utoipa::path(
194 put,
195 path = "/{id}",
196 operation_id = "updateExerciseRepository",
197 tag = "exercise_repositories",
198 params(
199 ("id" = Uuid, Path, description = "Exercise repository id")
200 ),
201 request_body = ExerciseRepositoryUpdate,
202 responses(
203 (status = 200, description = "Updated exercise repository", body = bool)
204 )
205)]
206#[instrument(skip(pool))]
207async fn update(
208 pool: web::Data<PgPool>,
209 id: web::Path<Uuid>,
210 user: Option<AuthUser>,
211 update: web::Json<ExerciseRepositoryUpdate>,
212) -> ControllerResult<web::Json<bool>> {
213 let mut conn = pool.acquire().await?;
214 let repository = models::exercise_repositories::get(&mut conn, *id).await?;
215 let course_or_exam_id =
216 CourseOrExamId::from_course_and_exam_ids(repository.course_id, repository.exam_id)?;
217 let token = authorize(
218 &mut conn,
219 Act::Edit,
220 user.map(|u| u.id),
221 Res::from_course_or_exam_id(course_or_exam_id),
222 )
223 .await?;
224
225 models::exercise_repositories::update(&mut conn, *id, update.deref()).await?;
226 token.authorized_ok(web::Json(true))
227}
228
229pub fn _add_routes(cfg: &mut ServiceConfig) {
237 cfg.route("/new", web::post().to(new))
238 .route("/course/{course_id}", web::get().to(get_for_course))
239 .route("/exam/{exam_id}", web::get().to(get_for_exam))
240 .route("/{id}", web::delete().to(delete))
241 .route("/{id}", web::put().to(update));
242}