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: Option<AuthUser>,
107) -> ControllerResult<web::Json<Vec<ExerciseRepository>>> {
108 let mut conn = pool.acquire().await?;
109 let token = authorize(
110 &mut conn,
111 Act::View,
112 user.map(|u| u.id),
113 Res::Course(*course_id),
114 )
115 .await?;
116 let repos = models::exercise_repositories::get_for_course_or_exam(
117 &mut conn,
118 CourseOrExamId::Course(*course_id),
119 )
120 .await?;
121 token.authorized_ok(web::Json(repos))
122}
123
124#[utoipa::path(
128 get,
129 path = "/exam/{exam_id}",
130 operation_id = "getExerciseRepositoriesForExam",
131 tag = "exercise_repositories",
132 params(
133 ("exam_id" = Uuid, Path, description = "Exam id")
134 ),
135 responses(
136 (status = 200, description = "Exercise repositories for exam", body = Vec<ExerciseRepository>)
137 )
138)]
139#[instrument(skip(pool))]
140async fn get_for_exam(
141 pool: web::Data<PgPool>,
142 exam_id: web::Path<Uuid>,
143 user: Option<AuthUser>,
144) -> ControllerResult<web::Json<Vec<ExerciseRepository>>> {
145 let mut conn = pool.acquire().await?;
146 let token = authorize(
147 &mut conn,
148 Act::View,
149 user.map(|u| u.id),
150 Res::Exam(*exam_id),
151 )
152 .await?;
153 let repos = models::exercise_repositories::get_for_course_or_exam(
154 &mut conn,
155 CourseOrExamId::Exam(*exam_id),
156 )
157 .await?;
158 token.authorized_ok(web::Json(repos))
159}
160
161#[utoipa::path(
165 delete,
166 path = "/{id}",
167 operation_id = "deleteExerciseRepository",
168 tag = "exercise_repositories",
169 params(
170 ("id" = Uuid, Path, description = "Exercise repository id")
171 ),
172 responses(
173 (status = 200, description = "Deleted exercise repository", body = bool)
174 )
175)]
176#[instrument(skip(pool))]
177async fn delete(
178 pool: web::Data<PgPool>,
179 id: web::Path<Uuid>,
180 user: Option<AuthUser>,
181) -> ControllerResult<web::Json<bool>> {
182 let mut conn = pool.acquire().await?;
183 let repository = models::exercise_repositories::get(&mut conn, *id).await?;
184 let course_or_exam_id =
185 CourseOrExamId::from_course_and_exam_ids(repository.course_id, repository.exam_id)?;
186 let token = authorize(
187 &mut conn,
188 Act::Edit,
189 user.map(|u| u.id),
190 Res::from_course_or_exam_id(course_or_exam_id),
191 )
192 .await?;
193
194 let mut tx = conn.begin().await?;
195 models::repository_exercises::delete_from_repository(&mut tx, *id).await?;
196 models::exercise_repositories::delete(&mut tx, *id).await?;
197 tx.commit().await?;
198
199 token.authorized_ok(web::Json(true))
200}
201
202#[utoipa::path(
206 put,
207 path = "/{id}",
208 operation_id = "updateExerciseRepository",
209 tag = "exercise_repositories",
210 params(
211 ("id" = Uuid, Path, description = "Exercise repository id")
212 ),
213 request_body = ExerciseRepositoryUpdate,
214 responses(
215 (status = 200, description = "Updated exercise repository", body = bool)
216 )
217)]
218#[instrument(skip(pool))]
219async fn update(
220 pool: web::Data<PgPool>,
221 id: web::Path<Uuid>,
222 user: Option<AuthUser>,
223 update: web::Json<ExerciseRepositoryUpdate>,
224) -> ControllerResult<web::Json<bool>> {
225 let mut conn = pool.acquire().await?;
226 let repository = models::exercise_repositories::get(&mut conn, *id).await?;
227 let course_or_exam_id =
228 CourseOrExamId::from_course_and_exam_ids(repository.course_id, repository.exam_id)?;
229 let token = authorize(
230 &mut conn,
231 Act::Edit,
232 user.map(|u| u.id),
233 Res::from_course_or_exam_id(course_or_exam_id),
234 )
235 .await?;
236
237 models::exercise_repositories::update(&mut conn, *id, update.deref()).await?;
238 token.authorized_ok(web::Json(true))
239}
240
241pub fn _add_routes(cfg: &mut ServiceConfig) {
249 cfg.route("/new", web::post().to(new))
250 .route("/course/{course_id}", web::get().to(get_for_course))
251 .route("/exam/{exam_id}", web::get().to(get_for_exam))
252 .route("/{id}", web::delete().to(delete))
253 .route("/{id}", web::put().to(update));
254}