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