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