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 new_email_template = payload.0;
67 let email_template = models::email_templates::insert_email_template(
68 &mut conn,
69 *course_instance_id,
70 new_email_template,
71 None,
72 )
73 .await?;
74 token.authorized_ok(web::Json(email_template))
75}
76
77#[instrument(skip(pool))]
78async fn get_email_templates_by_course_instance_id(
79 course_instance_id: web::Path<Uuid>,
80 pool: web::Data<PgPool>,
81 user: AuthUser,
82) -> ControllerResult<web::Json<Vec<EmailTemplate>>> {
83 let mut conn = pool.acquire().await?;
84 let token = authorize(
85 &mut conn,
86 Act::Edit,
87 Some(user.id),
88 Res::CourseInstance(*course_instance_id),
89 )
90 .await?;
91
92 let email_templates =
93 models::email_templates::get_email_templates(&mut conn, *course_instance_id).await?;
94 token.authorized_ok(web::Json(email_templates))
95}
96
97#[instrument(skip(pool))]
101pub async fn point_export(
102 course_instance_id: web::Path<Uuid>,
103 pool: web::Data<PgPool>,
104 user: AuthUser,
105) -> ControllerResult<HttpResponse> {
106 let mut conn = pool.acquire().await?;
107 let token = authorize(
108 &mut conn,
109 Act::Edit,
110 Some(user.id),
111 Res::CourseInstance(*course_instance_id),
112 )
113 .await?;
114
115 let course_instance =
116 course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
117 let course = courses::get_course(&mut conn, course_instance.course_id).await?;
118
119 general_export(
120 pool,
121 &format!(
122 "attachment; filename=\"{} - {} - Point export {}.csv\"",
123 course.name,
124 course_instance.name.as_deref().unwrap_or("unnamed"),
125 Utc::now().format("%Y-%m-%d")
126 ),
127 PointExportOperation {
128 course_instance_id: *course_instance_id,
129 },
130 token,
131 )
132 .await
133}
134
135#[instrument(skip(pool))]
136async fn points(
137 course_instance_id: web::Path<Uuid>,
138 pagination: web::Query<Pagination>,
139 pool: web::Data<PgPool>,
140 user: AuthUser,
141) -> ControllerResult<web::Json<Points>> {
142 let mut conn = pool.acquire().await?;
143 let token = authorize(
144 &mut conn,
145 Act::ViewUserProgressOrDetails,
146 Some(user.id),
147 Res::CourseInstance(*course_instance_id),
148 )
149 .await?;
150 let points = course_instances::get_points(&mut conn, *course_instance_id, *pagination).await?;
151 token.authorized_ok(web::Json(points))
152}
153
154#[instrument(skip(pool))]
158async fn completions(
159 course_instance_id: web::Path<Uuid>,
160 pool: web::Data<PgPool>,
161 user: AuthUser,
162) -> ControllerResult<web::Json<CourseInstanceCompletionSummary>> {
163 let mut conn = pool.acquire().await?;
164 let token = authorize(
165 &mut conn,
166 Act::ViewUserProgressOrDetails,
167 Some(user.id),
168 Res::CourseInstance(*course_instance_id),
169 )
170 .await?;
171 let course_instance =
172 course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
173 let completions =
174 library::progressing::get_course_instance_completion_summary(&mut conn, &course_instance)
175 .await?;
176 token.authorized_ok(web::Json(completions))
177}
178
179#[instrument(skip(pool, payload))]
183async fn post_completions(
184 course_instance_id: web::Path<Uuid>,
185 pool: web::Data<PgPool>,
186 user: AuthUser,
187 payload: web::Json<TeacherManualCompletionRequest>,
188) -> ControllerResult<web::Json<()>> {
189 let mut conn = pool.acquire().await?;
190 let token = authorize(
191 &mut conn,
192 Act::Edit,
193 Some(user.id),
194 Res::CourseInstance(*course_instance_id),
195 )
196 .await?;
197 let data = payload.0;
198 let course_instance =
199 course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
200 library::progressing::add_manual_completions(&mut conn, user.id, &course_instance, &data)
201 .await?;
202 token.authorized_ok(web::Json(()))
203}
204
205#[instrument(skip(pool, payload))]
206async fn preview_post_completions(
207 course_instance_id: web::Path<Uuid>,
208 pool: web::Data<PgPool>,
209 user: AuthUser,
210 payload: web::Json<TeacherManualCompletionRequest>,
211) -> ControllerResult<web::Json<ManualCompletionPreview>> {
212 let mut conn = pool.acquire().await?;
213 let token = authorize(
214 &mut conn,
215 Act::Edit,
216 Some(user.id),
217 Res::CourseInstance(*course_instance_id),
218 )
219 .await?;
220 let data = payload.0;
221 let instance = course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
222 let preview =
223 library::progressing::get_manual_completion_result_preview(&mut conn, &instance, &data)
224 .await?;
225 token.authorized_ok(web::Json(preview))
226}
227
228#[instrument(skip(pool))]
232pub async fn edit(
233 update: web::Json<CourseInstanceForm>,
234 course_instance_id: web::Path<Uuid>,
235 pool: web::Data<PgPool>,
236 user: AuthUser,
237) -> ControllerResult<HttpResponse> {
238 let mut conn = pool.acquire().await?;
239 let token = authorize(
240 &mut conn,
241 Act::Edit,
242 Some(user.id),
243 Res::CourseInstance(*course_instance_id),
244 )
245 .await?;
246 course_instances::edit(&mut conn, *course_instance_id, update.into_inner()).await?;
247 token.authorized_ok(HttpResponse::Ok().finish())
248}
249
250#[instrument(skip(pool))]
254async fn delete(
255 id: web::Path<Uuid>,
256 pool: web::Data<PgPool>,
257 user: AuthUser,
258) -> ControllerResult<HttpResponse> {
259 let mut conn = pool.acquire().await?;
260 let token = authorize(
261 &mut conn,
262 Act::Edit,
263 Some(user.id),
264 Res::CourseInstance(*id),
265 )
266 .await?;
267 models::course_instances::delete(&mut conn, *id).await?;
268 token.authorized_ok(HttpResponse::Ok().finish())
269}
270
271#[instrument(skip(pool))]
275pub async fn completions_export(
276 course_instance_id: web::Path<Uuid>,
277 pool: web::Data<PgPool>,
278 user: AuthUser,
279) -> ControllerResult<HttpResponse> {
280 let mut conn = pool.acquire().await?;
281 let token = authorize(
282 &mut conn,
283 Act::Edit,
284 Some(user.id),
285 Res::CourseInstance(*course_instance_id),
286 )
287 .await?;
288
289 let course_instance =
290 course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
291 let course = courses::get_course(&mut conn, course_instance.course_id).await?;
292
293 general_export(
294 pool,
295 &format!(
296 "attachment; filename=\"{} - {} - Completions export {}.csv\"",
297 course.name,
298 course_instance.name.as_deref().unwrap_or("unnamed"),
299 Utc::now().format("%Y-%m-%d")
300 ),
301 CompletionsExportOperation {
302 course_instance_id: *course_instance_id,
303 },
304 token,
305 )
306 .await
307}
308#[instrument(skip(pool))]
312pub async fn certificate_configurations(
313 course_instance_id: web::Path<Uuid>,
314 pool: web::Data<PgPool>,
315 user: AuthUser,
316) -> ControllerResult<web::Json<Vec<CertificateConfigurationAndRequirements>>> {
317 let mut conn = pool.acquire().await?;
318 let token = authorize(
319 &mut conn,
320 Act::Teach,
321 Some(user.id),
322 Res::CourseInstance(*course_instance_id),
323 )
324 .await?;
325
326 let course_instance =
327 models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
328
329 let certificate_configurations =
330 models::certificate_configurations::get_default_certificate_configurations_and_requirements_by_course(
331 &mut conn,
332 course_instance.course_id,
333 )
334 .await?;
335 token.authorized_ok(web::Json(certificate_configurations))
336}
337
338#[instrument(skip(pool))]
342
343async fn get_all_exercise_statuses_by_course_instance_id(
344 params: web::Path<(Uuid, Uuid)>,
345 pool: web::Data<PgPool>,
346 user: AuthUser,
347) -> ControllerResult<web::Json<Vec<ExerciseStatusSummaryForUser>>> {
348 let (course_instance_id, user_id) = params.into_inner();
349 let mut conn = pool.acquire().await?;
350 let token = authorize(
351 &mut conn,
352 Act::ViewUserProgressOrDetails,
353 Some(user.id),
354 Res::CourseInstance(course_instance_id),
355 )
356 .await?;
357
358 let course_instance =
359 models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
360
361 let res = models::exercises::get_all_exercise_statuses_by_user_id_and_course_id(
362 &mut conn,
363 course_instance.course_id,
364 user_id,
365 )
366 .await?;
367
368 token.authorized_ok(web::Json(res))
369}
370
371#[instrument(skip(pool))]
375
376async fn get_all_get_all_course_module_completions_for_user_by_course_instance_id(
377 params: web::Path<(Uuid, Uuid)>,
378 pool: web::Data<PgPool>,
379 user: AuthUser,
380) -> ControllerResult<web::Json<Vec<CourseModuleCompletion>>> {
381 let (course_instance_id, user_id) = params.into_inner();
382 let mut conn = pool.acquire().await?;
383 let token = authorize(
384 &mut conn,
385 Act::ViewUserProgressOrDetails,
386 Some(user.id),
387 Res::CourseInstance(course_instance_id),
388 )
389 .await?;
390
391 let course_instance =
392 models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
393
394 let res = models::course_module_completions::get_all_by_course_id_and_user_id(
395 &mut conn,
396 course_instance.course_id,
397 user_id,
398 )
399 .await?;
400
401 token.authorized_ok(web::Json(res))
402}
403
404#[instrument(skip(pool))]
408async fn get_user_progress_for_course_instance(
409 user: AuthUser,
410 params: web::Path<(Uuid, Uuid)>,
411 pool: web::Data<PgPool>,
412) -> ControllerResult<web::Json<Vec<UserCourseProgress>>> {
413 let (course_instance_id, user_id) = params.into_inner();
414 let mut conn = pool.acquire().await?;
415 let token = authorize(
416 &mut conn,
417 Act::ViewUserProgressOrDetails,
418 Some(user.id),
419 Res::CourseInstance(course_instance_id),
420 )
421 .await?;
422
423 let course_instance =
424 models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
425
426 let user_course_progress = models::user_exercise_states::get_user_course_progress(
427 &mut conn,
428 course_instance.course_id,
429 user_id,
430 )
431 .await?;
432 token.authorized_ok(web::Json(user_course_progress))
433}
434
435pub fn _add_routes(cfg: &mut ServiceConfig) {
443 cfg.route("/{course_instance_id}", web::get().to(get_course_instance))
444 .route(
445 "/{course_instance_id}/email-templates",
446 web::post().to(post_new_email_template),
447 )
448 .route(
449 "/{course_instance_id}/email-templates",
450 web::get().to(get_email_templates_by_course_instance_id),
451 )
452 .route(
453 "/{course_instance_id}/export-points",
454 web::get().to(point_export),
455 )
456 .route("/{course_instance_id}/edit", web::post().to(edit))
457 .route("/{course_instance_id}/delete", web::post().to(delete))
458 .route(
459 "/{course_instance_id}/completions",
460 web::get().to(completions),
461 )
462 .route(
463 "/{course_instance_id}/export-completions",
464 web::get().to(completions_export),
465 )
466 .route(
467 "/{course_instance_id}/completions",
468 web::post().to(post_completions),
469 )
470 .route(
471 "/{course_instance_id}/completions/preview",
472 web::post().to(preview_post_completions),
473 )
474 .route("/{course_instance_id}/points", web::get().to(points))
475 .route(
476 "/{course_instance_id}/status-for-all-exercises/{user_id}",
477 web::get().to(get_all_exercise_statuses_by_course_instance_id),
478 )
479 .route(
480 "/{course_instance_id}/course-module-completions/{user_id}",
481 web::get().to(get_all_get_all_course_module_completions_for_user_by_course_instance_id),
482 )
483 .route(
484 "/{course_instance_id}/progress/{user_id}",
485 web::get().to(get_user_progress_for_course_instance),
486 )
487 .route(
488 "/{course_instance_id}/default-certificate-configurations",
489 web::get().to(certificate_configurations),
490 );
491}