1use chrono::Utc;
4use models::{
5 certificate_configurations::CertificateConfigurationAndRequirements,
6 course_instances::{self, CourseInstance, CourseInstanceForm, Points},
7 course_module_completions::CourseModuleCompletion,
8 courses,
9 email_templates::{EmailTemplate, EmailTemplateNew},
10 exercises::ExerciseStatusSummaryForUser,
11 library::{
12 self,
13 progressing::{
14 CourseInstanceCompletionSummary, ManualCompletionPreview,
15 TeacherManualCompletionRequest,
16 },
17 },
18 user_exercise_states::UserCourseProgress,
19};
20
21use crate::{
22 domain::csv_export::{
23 course_instance_export::CompletionsExportOperation, general_export,
24 points::PointExportOperation,
25 },
26 prelude::*,
27};
28
29#[instrument(skip(pool))]
33async fn get_course_instance(
34 course_instance_id: web::Path<Uuid>,
35 user: AuthUser,
36 pool: web::Data<PgPool>,
37) -> ControllerResult<web::Json<CourseInstance>> {
38 let mut conn = pool.acquire().await?;
39 let token = authorize(
40 &mut conn,
41 Act::Edit,
42 Some(user.id),
43 Res::CourseInstance(*course_instance_id),
44 )
45 .await?;
46 let course_instance =
47 models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
48 token.authorized_ok(web::Json(course_instance))
49}
50
51#[instrument(skip(payload, pool))]
52async fn post_new_email_template(
53 course_instance_id: web::Path<Uuid>,
54 payload: web::Json<EmailTemplateNew>,
55 pool: web::Data<PgPool>,
56 user: AuthUser,
57) -> ControllerResult<web::Json<EmailTemplate>> {
58 let mut conn = pool.acquire().await?;
59 let token = authorize(
60 &mut conn,
61 Act::Edit,
62 Some(user.id),
63 Res::CourseInstance(*course_instance_id),
64 )
65 .await?;
66 let course_instance =
67 models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
68 let new_email_template = payload.0;
69 let email_template = models::email_templates::insert_email_template(
70 &mut conn,
71 Some(course_instance.course_id),
72 new_email_template,
73 None,
74 )
75 .await?;
76 token.authorized_ok(web::Json(email_template))
77}
78
79#[instrument(skip(pool))]
80async fn get_email_templates_by_course_instance_id(
81 course_instance_id: web::Path<Uuid>,
82 pool: web::Data<PgPool>,
83 user: AuthUser,
84) -> ControllerResult<web::Json<Vec<EmailTemplate>>> {
85 let mut conn = pool.acquire().await?;
86 let token = authorize(
87 &mut conn,
88 Act::Edit,
89 Some(user.id),
90 Res::CourseInstance(*course_instance_id),
91 )
92 .await?;
93
94 let course_instance =
95 models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
96 let email_templates =
97 models::email_templates::get_email_templates(&mut conn, course_instance.course_id).await?;
98 token.authorized_ok(web::Json(email_templates))
99}
100
101#[instrument(skip(pool))]
105pub async fn point_export(
106 course_instance_id: web::Path<Uuid>,
107 pool: web::Data<PgPool>,
108 user: AuthUser,
109) -> ControllerResult<HttpResponse> {
110 let mut conn = pool.acquire().await?;
111 let token = authorize(
112 &mut conn,
113 Act::Edit,
114 Some(user.id),
115 Res::CourseInstance(*course_instance_id),
116 )
117 .await?;
118
119 let course_instance =
120 course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
121 let course = courses::get_course(&mut conn, course_instance.course_id).await?;
122
123 general_export(
124 pool,
125 &format!(
126 "attachment; filename=\"{} - {} - Point export {}.csv\"",
127 course.name,
128 course_instance.name.as_deref().unwrap_or("unnamed"),
129 Utc::now().format("%Y-%m-%d")
130 ),
131 PointExportOperation {
132 course_instance_id: *course_instance_id,
133 },
134 token,
135 )
136 .await
137}
138
139#[instrument(skip(pool))]
140async fn points(
141 course_instance_id: web::Path<Uuid>,
142 pagination: web::Query<Pagination>,
143 pool: web::Data<PgPool>,
144 user: AuthUser,
145) -> ControllerResult<web::Json<Points>> {
146 let mut conn = pool.acquire().await?;
147 let token = authorize(
148 &mut conn,
149 Act::ViewUserProgressOrDetails,
150 Some(user.id),
151 Res::CourseInstance(*course_instance_id),
152 )
153 .await?;
154 let points = course_instances::get_points(&mut conn, *course_instance_id, *pagination).await?;
155 token.authorized_ok(web::Json(points))
156}
157
158#[instrument(skip(pool))]
162async fn completions(
163 course_instance_id: web::Path<Uuid>,
164 pool: web::Data<PgPool>,
165 user: AuthUser,
166) -> ControllerResult<web::Json<CourseInstanceCompletionSummary>> {
167 let mut conn = pool.acquire().await?;
168 let token = authorize(
169 &mut conn,
170 Act::ViewUserProgressOrDetails,
171 Some(user.id),
172 Res::CourseInstance(*course_instance_id),
173 )
174 .await?;
175 let course_instance =
176 course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
177 let completions =
178 library::progressing::get_course_instance_completion_summary(&mut conn, &course_instance)
179 .await?;
180 token.authorized_ok(web::Json(completions))
181}
182
183#[instrument(skip(pool, payload))]
187async fn post_completions(
188 course_instance_id: web::Path<Uuid>,
189 pool: web::Data<PgPool>,
190 user: AuthUser,
191 payload: web::Json<TeacherManualCompletionRequest>,
192) -> ControllerResult<web::Json<()>> {
193 let mut conn = pool.acquire().await?;
194 let token = authorize(
195 &mut conn,
196 Act::Edit,
197 Some(user.id),
198 Res::CourseInstance(*course_instance_id),
199 )
200 .await?;
201 let data = payload.0;
202 let course_instance =
203 course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
204 library::progressing::add_manual_completions(&mut conn, user.id, &course_instance, &data)
205 .await?;
206 token.authorized_ok(web::Json(()))
207}
208
209#[instrument(skip(pool, payload))]
210async fn preview_post_completions(
211 course_instance_id: web::Path<Uuid>,
212 pool: web::Data<PgPool>,
213 user: AuthUser,
214 payload: web::Json<TeacherManualCompletionRequest>,
215) -> ControllerResult<web::Json<ManualCompletionPreview>> {
216 let mut conn = pool.acquire().await?;
217 let token = authorize(
218 &mut conn,
219 Act::Edit,
220 Some(user.id),
221 Res::CourseInstance(*course_instance_id),
222 )
223 .await?;
224 let data = payload.0;
225 let instance = course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
226 let preview =
227 library::progressing::get_manual_completion_result_preview(&mut conn, &instance, &data)
228 .await?;
229 token.authorized_ok(web::Json(preview))
230}
231
232#[instrument(skip(pool))]
236pub async fn edit(
237 update: web::Json<CourseInstanceForm>,
238 course_instance_id: web::Path<Uuid>,
239 pool: web::Data<PgPool>,
240 user: AuthUser,
241) -> ControllerResult<HttpResponse> {
242 let mut conn = pool.acquire().await?;
243 let token = authorize(
244 &mut conn,
245 Act::Edit,
246 Some(user.id),
247 Res::CourseInstance(*course_instance_id),
248 )
249 .await?;
250 course_instances::edit(&mut conn, *course_instance_id, update.into_inner()).await?;
251 token.authorized_ok(HttpResponse::Ok().finish())
252}
253
254#[instrument(skip(pool))]
258async fn delete(
259 id: web::Path<Uuid>,
260 pool: web::Data<PgPool>,
261 user: AuthUser,
262) -> ControllerResult<HttpResponse> {
263 let mut conn = pool.acquire().await?;
264 let token = authorize(
265 &mut conn,
266 Act::Edit,
267 Some(user.id),
268 Res::CourseInstance(*id),
269 )
270 .await?;
271 models::course_instances::delete(&mut conn, *id).await?;
272 token.authorized_ok(HttpResponse::Ok().finish())
273}
274
275#[instrument(skip(pool))]
279pub async fn completions_export(
280 course_instance_id: web::Path<Uuid>,
281 pool: web::Data<PgPool>,
282 user: AuthUser,
283) -> ControllerResult<HttpResponse> {
284 let mut conn = pool.acquire().await?;
285 let token = authorize(
286 &mut conn,
287 Act::Edit,
288 Some(user.id),
289 Res::CourseInstance(*course_instance_id),
290 )
291 .await?;
292
293 let course_instance =
294 course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
295 let course = courses::get_course(&mut conn, course_instance.course_id).await?;
296
297 general_export(
298 pool,
299 &format!(
300 "attachment; filename=\"{} - {} - Completions export {}.csv\"",
301 course.name,
302 course_instance.name.as_deref().unwrap_or("unnamed"),
303 Utc::now().format("%Y-%m-%d")
304 ),
305 CompletionsExportOperation {
306 course_instance_id: *course_instance_id,
307 },
308 token,
309 )
310 .await
311}
312#[instrument(skip(pool))]
316pub async fn certificate_configurations(
317 course_instance_id: web::Path<Uuid>,
318 pool: web::Data<PgPool>,
319 user: AuthUser,
320) -> ControllerResult<web::Json<Vec<CertificateConfigurationAndRequirements>>> {
321 let mut conn = pool.acquire().await?;
322 let token = authorize(
323 &mut conn,
324 Act::Teach,
325 Some(user.id),
326 Res::CourseInstance(*course_instance_id),
327 )
328 .await?;
329
330 let course_instance =
331 models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
332
333 let certificate_configurations =
334 models::certificate_configurations::get_default_certificate_configurations_and_requirements_by_course(
335 &mut conn,
336 course_instance.course_id,
337 )
338 .await?;
339 token.authorized_ok(web::Json(certificate_configurations))
340}
341
342#[instrument(skip(pool))]
346
347async fn get_all_exercise_statuses_by_course_instance_id(
348 params: web::Path<(Uuid, Uuid)>,
349 pool: web::Data<PgPool>,
350 user: AuthUser,
351) -> ControllerResult<web::Json<Vec<ExerciseStatusSummaryForUser>>> {
352 let (course_instance_id, user_id) = params.into_inner();
353 let mut conn = pool.acquire().await?;
354 let token = authorize(
355 &mut conn,
356 Act::ViewUserProgressOrDetails,
357 Some(user.id),
358 Res::CourseInstance(course_instance_id),
359 )
360 .await?;
361
362 let course_instance =
363 models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
364
365 let res = models::exercises::get_all_exercise_statuses_by_user_id_and_course_id(
366 &mut conn,
367 course_instance.course_id,
368 user_id,
369 )
370 .await?;
371
372 token.authorized_ok(web::Json(res))
373}
374
375#[instrument(skip(pool))]
379
380async fn get_all_get_all_course_module_completions_for_user_by_course_instance_id(
381 params: web::Path<(Uuid, Uuid)>,
382 pool: web::Data<PgPool>,
383 user: AuthUser,
384) -> ControllerResult<web::Json<Vec<CourseModuleCompletion>>> {
385 let (course_instance_id, user_id) = params.into_inner();
386 let mut conn = pool.acquire().await?;
387 let token = authorize(
388 &mut conn,
389 Act::ViewUserProgressOrDetails,
390 Some(user.id),
391 Res::CourseInstance(course_instance_id),
392 )
393 .await?;
394
395 let course_instance =
396 models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
397
398 let res = models::course_module_completions::get_all_by_course_id_and_user_id(
399 &mut conn,
400 course_instance.course_id,
401 user_id,
402 )
403 .await?;
404
405 token.authorized_ok(web::Json(res))
406}
407
408#[instrument(skip(pool))]
412async fn get_user_progress_for_course_instance(
413 user: AuthUser,
414 params: web::Path<(Uuid, Uuid)>,
415 pool: web::Data<PgPool>,
416) -> ControllerResult<web::Json<Vec<UserCourseProgress>>> {
417 let (course_instance_id, user_id) = params.into_inner();
418 let mut conn = pool.acquire().await?;
419 let token = authorize(
420 &mut conn,
421 Act::ViewUserProgressOrDetails,
422 Some(user.id),
423 Res::CourseInstance(course_instance_id),
424 )
425 .await?;
426
427 let course_instance =
428 models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
429
430 let user_course_progress = models::user_exercise_states::get_user_course_progress(
431 &mut conn,
432 course_instance.course_id,
433 user_id,
434 false,
435 )
436 .await?;
437 token.authorized_ok(web::Json(user_course_progress))
438}
439
440pub fn _add_routes(cfg: &mut ServiceConfig) {
448 cfg.route("/{course_instance_id}", web::get().to(get_course_instance))
449 .route(
450 "/{course_instance_id}/email-templates",
451 web::post().to(post_new_email_template),
452 )
453 .route(
454 "/{course_instance_id}/email-templates",
455 web::get().to(get_email_templates_by_course_instance_id),
456 )
457 .route(
458 "/{course_instance_id}/export-points",
459 web::get().to(point_export),
460 )
461 .route("/{course_instance_id}/edit", web::post().to(edit))
462 .route("/{course_instance_id}/delete", web::post().to(delete))
463 .route(
464 "/{course_instance_id}/completions",
465 web::get().to(completions),
466 )
467 .route(
468 "/{course_instance_id}/export-completions",
469 web::get().to(completions_export),
470 )
471 .route(
472 "/{course_instance_id}/completions",
473 web::post().to(post_completions),
474 )
475 .route(
476 "/{course_instance_id}/completions/preview",
477 web::post().to(preview_post_completions),
478 )
479 .route("/{course_instance_id}/points", web::get().to(points))
480 .route(
481 "/{course_instance_id}/status-for-all-exercises/{user_id}",
482 web::get().to(get_all_exercise_statuses_by_course_instance_id),
483 )
484 .route(
485 "/{course_instance_id}/course-module-completions/{user_id}",
486 web::get().to(get_all_get_all_course_module_completions_for_user_by_course_instance_id),
487 )
488 .route(
489 "/{course_instance_id}/progress/{user_id}",
490 web::get().to(get_user_progress_for_course_instance),
491 )
492 .route(
493 "/{course_instance_id}/default-certificate-configurations",
494 web::get().to(certificate_configurations),
495 );
496}