Skip to main content

headless_lms_server/controllers/main_frontend/
course_instances.rs

1//! Controllers for requests starting with `/api/v0/main-frontend/course-instances`.
2
3use 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};
20use utoipa::OpenApi;
21
22use crate::{
23    controllers::helpers::email_content::parse_email_content,
24    domain::csv_export::{
25        course_instance_export::CompletionsExportOperation, general_export,
26        points::PointExportOperation,
27    },
28    prelude::*,
29};
30
31#[derive(OpenApi)]
32#[openapi(paths(
33    get_course_instance,
34    post_new_email_template,
35    get_email_templates_by_course_instance_id,
36    point_export,
37    points,
38    completions,
39    post_completions,
40    preview_post_completions,
41    edit,
42    delete,
43    completions_export,
44    certificate_configurations,
45    get_all_exercise_statuses_by_course_instance_id,
46    get_all_get_all_course_module_completions_for_user_by_course_instance_id,
47    get_user_progress_for_course_instance
48))]
49pub(crate) struct MainFrontendCourseInstancesApiDoc;
50
51/**
52GET /course-instances/:id
53*/
54#[instrument(skip(pool))]
55#[utoipa::path(
56    get,
57    path = "/{course_instance_id}",
58    operation_id = "getCourseInstance",
59    tag = "course-instances",
60    params(
61        ("course_instance_id" = Uuid, Path, description = "Course instance id")
62    ),
63    responses(
64        (status = 200, description = "Course instance", body = CourseInstance)
65    )
66)]
67async fn get_course_instance(
68    course_instance_id: web::Path<Uuid>,
69    user: AuthUser,
70    pool: web::Data<PgPool>,
71) -> ControllerResult<web::Json<CourseInstance>> {
72    let mut conn = pool.acquire().await?;
73    let token = authorize(
74        &mut conn,
75        Act::ViewUserProgressOrDetails,
76        Some(user.id),
77        Res::CourseInstance(*course_instance_id),
78    )
79    .await?;
80    let course_instance =
81        models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
82    token.authorized_ok(web::Json(course_instance))
83}
84
85#[instrument(skip(payload, pool))]
86#[utoipa::path(
87    post,
88    path = "/{course_instance_id}/email-templates",
89    operation_id = "createCourseInstanceEmailTemplate",
90    tag = "course-instances",
91    params(
92        ("course_instance_id" = Uuid, Path, description = "Course instance id")
93    ),
94    request_body = EmailTemplateNew,
95    responses(
96        (status = 200, description = "Created email template", body = EmailTemplate)
97    )
98)]
99async fn post_new_email_template(
100    course_instance_id: web::Path<Uuid>,
101    payload: web::Json<EmailTemplateNew>,
102    pool: web::Data<PgPool>,
103    user: AuthUser,
104) -> ControllerResult<web::Json<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    let course_instance =
114        models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
115    let new_email_template = payload.0;
116    if let Some(content) = &new_email_template.content {
117        parse_email_content(content)?;
118    }
119    let email_template = models::email_templates::insert_email_template(
120        &mut conn,
121        Some(course_instance.course_id),
122        new_email_template,
123        None,
124    )
125    .await?;
126    token.authorized_ok(web::Json(email_template))
127}
128
129#[instrument(skip(pool))]
130#[utoipa::path(
131    get,
132    path = "/{course_instance_id}/email-templates",
133    operation_id = "getCourseInstanceEmailTemplates",
134    tag = "course-instances",
135    params(
136        ("course_instance_id" = Uuid, Path, description = "Course instance id")
137    ),
138    responses(
139        (status = 200, description = "Course instance email templates", body = [EmailTemplate])
140    )
141)]
142async fn get_email_templates_by_course_instance_id(
143    course_instance_id: web::Path<Uuid>,
144    pool: web::Data<PgPool>,
145    user: AuthUser,
146) -> ControllerResult<web::Json<Vec<EmailTemplate>>> {
147    let mut conn = pool.acquire().await?;
148    let token = authorize(
149        &mut conn,
150        Act::Edit,
151        Some(user.id),
152        Res::CourseInstance(*course_instance_id),
153    )
154    .await?;
155
156    let course_instance =
157        models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
158    let email_templates =
159        models::email_templates::get_email_templates(&mut conn, course_instance.course_id).await?;
160    token.authorized_ok(web::Json(email_templates))
161}
162
163/**
164GET `/api/v0/main-frontend/course-instances/${courseInstanceId}/export-points` - gets CSV of course instance points based on course_instance ID.
165*/
166#[instrument(skip(pool))]
167#[utoipa::path(
168    get,
169    path = "/{course_instance_id}/export-points",
170    operation_id = "exportCourseInstancePointsCsv",
171    tag = "course-instances",
172    params(
173        ("course_instance_id" = Uuid, Path, description = "Course instance id")
174    ),
175    responses(
176        (status = 200, description = "Course instance points CSV", body = String, content_type = "text/csv")
177    )
178)]
179pub async fn point_export(
180    course_instance_id: web::Path<Uuid>,
181    pool: web::Data<PgPool>,
182    user: AuthUser,
183) -> ControllerResult<HttpResponse> {
184    let mut conn = pool.acquire().await?;
185    let token = authorize(
186        &mut conn,
187        Act::Edit,
188        Some(user.id),
189        Res::CourseInstance(*course_instance_id),
190    )
191    .await?;
192
193    let course_instance =
194        course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
195    let course = courses::get_course(&mut conn, course_instance.course_id).await?;
196
197    general_export(
198        pool,
199        &format!(
200            "attachment; filename=\"{} - {} - Point export {}.csv\"",
201            course.name,
202            course_instance.name.as_deref().unwrap_or("unnamed"),
203            Utc::now().format("%Y-%m-%d")
204        ),
205        PointExportOperation {
206            course_instance_id: *course_instance_id,
207        },
208        token,
209    )
210    .await
211}
212
213#[instrument(skip(pool))]
214#[utoipa::path(
215    get,
216    path = "/{course_instance_id}/points",
217    operation_id = "getCourseInstancePoints",
218    tag = "course-instances",
219    params(
220        ("course_instance_id" = Uuid, Path, description = "Course instance id"),
221        ("page" = Option<i64>, Query, description = "Page number"),
222        ("limit" = Option<i64>, Query, description = "Page size")
223    ),
224    responses(
225        (status = 200, description = "Course instance points", body = Points)
226    )
227)]
228async fn points(
229    course_instance_id: web::Path<Uuid>,
230    pagination: web::Query<Pagination>,
231    pool: web::Data<PgPool>,
232    user: AuthUser,
233) -> ControllerResult<web::Json<Points>> {
234    let mut conn = pool.acquire().await?;
235    let token = authorize(
236        &mut conn,
237        Act::ViewUserProgressOrDetails,
238        Some(user.id),
239        Res::CourseInstance(*course_instance_id),
240    )
241    .await?;
242    let points = course_instances::get_points(&mut conn, *course_instance_id, *pagination).await?;
243    token.authorized_ok(web::Json(points))
244}
245
246/**
247GET `/api/v0/main-frontend/course-instances/{course_instance_id}/completions`
248*/
249#[instrument(skip(pool))]
250#[utoipa::path(
251    get,
252    path = "/{course_instance_id}/completions",
253    operation_id = "getCourseInstanceCompletions",
254    tag = "course-instances",
255    params(
256        ("course_instance_id" = Uuid, Path, description = "Course instance id")
257    ),
258    responses(
259        (status = 200, description = "Course instance completion summary", body = CourseInstanceCompletionSummary)
260    )
261)]
262async fn completions(
263    course_instance_id: web::Path<Uuid>,
264    pool: web::Data<PgPool>,
265    user: AuthUser,
266) -> ControllerResult<web::Json<CourseInstanceCompletionSummary>> {
267    let mut conn = pool.acquire().await?;
268    let token = authorize(
269        &mut conn,
270        Act::ViewUserProgressOrDetails,
271        Some(user.id),
272        Res::CourseInstance(*course_instance_id),
273    )
274    .await?;
275    let course_instance =
276        course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
277    let completions =
278        library::progressing::get_course_instance_completion_summary(&mut conn, &course_instance)
279            .await?;
280    token.authorized_ok(web::Json(completions))
281}
282
283/**
284POST `/api/v0/main-frontend/course-instances/{course_instance_id}/completions`
285*/
286#[instrument(skip(pool, payload))]
287#[utoipa::path(
288    post,
289    path = "/{course_instance_id}/completions",
290    operation_id = "createCourseInstanceCompletions",
291    tag = "course-instances",
292    params(
293        ("course_instance_id" = Uuid, Path, description = "Course instance id")
294    ),
295    request_body = TeacherManualCompletionRequest,
296    responses(
297        (status = 200, description = "Manual completions added")
298    )
299)]
300async fn post_completions(
301    course_instance_id: web::Path<Uuid>,
302    pool: web::Data<PgPool>,
303    user: AuthUser,
304    payload: web::Json<TeacherManualCompletionRequest>,
305) -> ControllerResult<web::Json<()>> {
306    let mut conn = pool.acquire().await?;
307    let token = authorize(
308        &mut conn,
309        Act::Edit,
310        Some(user.id),
311        Res::CourseInstance(*course_instance_id),
312    )
313    .await?;
314    let data = payload.0;
315    let course_instance =
316        course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
317    library::progressing::add_manual_completions(&mut conn, user.id, &course_instance, &data)
318        .await?;
319    token.authorized_ok(web::Json(()))
320}
321
322#[instrument(skip(pool, payload))]
323#[utoipa::path(
324    post,
325    path = "/{course_instance_id}/completions/preview",
326    operation_id = "previewCourseInstanceCompletions",
327    tag = "course-instances",
328    params(
329        ("course_instance_id" = Uuid, Path, description = "Course instance id")
330    ),
331    request_body = TeacherManualCompletionRequest,
332    responses(
333        (status = 200, description = "Manual completion preview", body = ManualCompletionPreview)
334    )
335)]
336async fn preview_post_completions(
337    course_instance_id: web::Path<Uuid>,
338    pool: web::Data<PgPool>,
339    user: AuthUser,
340    payload: web::Json<TeacherManualCompletionRequest>,
341) -> ControllerResult<web::Json<ManualCompletionPreview>> {
342    let mut conn = pool.acquire().await?;
343    let token = authorize(
344        &mut conn,
345        Act::Edit,
346        Some(user.id),
347        Res::CourseInstance(*course_instance_id),
348    )
349    .await?;
350    let data = payload.0;
351    let instance = course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
352    let preview =
353        library::progressing::get_manual_completion_result_preview(&mut conn, &instance, &data)
354            .await?;
355    token.authorized_ok(web::Json(preview))
356}
357
358/**
359POST /course-instances/:id/edit
360*/
361#[instrument(skip(pool))]
362#[utoipa::path(
363    post,
364    path = "/{course_instance_id}/edit",
365    operation_id = "editCourseInstance",
366    tag = "course-instances",
367    params(
368        ("course_instance_id" = Uuid, Path, description = "Course instance id")
369    ),
370    request_body = CourseInstanceForm,
371    responses(
372        (status = 200, description = "Course instance updated")
373    )
374)]
375pub async fn edit(
376    update: web::Json<CourseInstanceForm>,
377    course_instance_id: web::Path<Uuid>,
378    pool: web::Data<PgPool>,
379    user: AuthUser,
380) -> ControllerResult<HttpResponse> {
381    let mut conn = pool.acquire().await?;
382    let token = authorize(
383        &mut conn,
384        Act::Edit,
385        Some(user.id),
386        Res::CourseInstance(*course_instance_id),
387    )
388    .await?;
389    course_instances::edit(&mut conn, *course_instance_id, update.into_inner()).await?;
390    token.authorized_ok(HttpResponse::Ok().finish())
391}
392
393/**
394POST /course-instances/:id/delete
395*/
396#[instrument(skip(pool))]
397#[utoipa::path(
398    post,
399    path = "/{course_instance_id}/delete",
400    operation_id = "deleteCourseInstance",
401    tag = "course-instances",
402    params(
403        ("course_instance_id" = Uuid, Path, description = "Course instance id")
404    ),
405    responses(
406        (status = 200, description = "Course instance deleted")
407    )
408)]
409async fn delete(
410    id: web::Path<Uuid>,
411    pool: web::Data<PgPool>,
412    user: AuthUser,
413) -> ControllerResult<HttpResponse> {
414    let mut conn = pool.acquire().await?;
415    let token = authorize(
416        &mut conn,
417        Act::Edit,
418        Some(user.id),
419        Res::CourseInstance(*id),
420    )
421    .await?;
422    models::course_instances::delete(&mut conn, *id).await?;
423    token.authorized_ok(HttpResponse::Ok().finish())
424}
425
426/**
427GET /course-instances/:id/export-completions - gets CSV of course completion based on course_instance ID.
428*/
429#[instrument(skip(pool))]
430#[utoipa::path(
431    get,
432    path = "/{course_instance_id}/export-completions",
433    operation_id = "exportCourseInstanceCompletionsCsv",
434    tag = "course-instances",
435    params(
436        ("course_instance_id" = Uuid, Path, description = "Course instance id")
437    ),
438    responses(
439        (status = 200, description = "Course instance completions CSV", body = String, content_type = "text/csv")
440    )
441)]
442pub async fn completions_export(
443    course_instance_id: web::Path<Uuid>,
444    pool: web::Data<PgPool>,
445    user: AuthUser,
446) -> ControllerResult<HttpResponse> {
447    let mut conn = pool.acquire().await?;
448    let token = authorize(
449        &mut conn,
450        Act::Edit,
451        Some(user.id),
452        Res::CourseInstance(*course_instance_id),
453    )
454    .await?;
455
456    let course_instance =
457        course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
458    let course = courses::get_course(&mut conn, course_instance.course_id).await?;
459
460    general_export(
461        pool,
462        &format!(
463            "attachment; filename=\"{} - {} - Completions export {}.csv\"",
464            course.name,
465            course_instance.name.as_deref().unwrap_or("unnamed"),
466            Utc::now().format("%Y-%m-%d")
467        ),
468        CompletionsExportOperation {
469            course_instance_id: *course_instance_id,
470        },
471        token,
472    )
473    .await
474}
475/**
476GET /course-instances/:id/default-certificate-configurations - gets default certificate configurations of the given course instance. A default certificate configuration requires only one course module to be completed.
477*/
478#[instrument(skip(pool))]
479#[utoipa::path(
480    get,
481    path = "/{course_instance_id}/default-certificate-configurations",
482    operation_id = "getCourseInstanceDefaultCertificateConfigurations",
483    tag = "course-instances",
484    params(
485        ("course_instance_id" = Uuid, Path, description = "Course instance id")
486    ),
487    responses(
488        (status = 200, description = "Default certificate configurations", body = [CertificateConfigurationAndRequirements])
489    )
490)]
491pub async fn certificate_configurations(
492    course_instance_id: web::Path<Uuid>,
493    pool: web::Data<PgPool>,
494    user: AuthUser,
495) -> ControllerResult<web::Json<Vec<CertificateConfigurationAndRequirements>>> {
496    let mut conn = pool.acquire().await?;
497    let token = authorize(
498        &mut conn,
499        Act::Teach,
500        Some(user.id),
501        Res::CourseInstance(*course_instance_id),
502    )
503    .await?;
504
505    let course_instance =
506        models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
507
508    let certificate_configurations =
509        models::certificate_configurations::get_default_certificate_configurations_and_requirements_by_course(
510            &mut conn,
511            course_instance.course_id,
512        )
513        .await?;
514    token.authorized_ok(web::Json(certificate_configurations))
515}
516
517/**
518GET /course-instances/:id/status-for-all-exercises/:user_id - Returns a status for all exercises in a course instance for a given user.
519*/
520#[instrument(skip(pool))]
521#[utoipa::path(
522    get,
523    path = "/{course_instance_id}/status-for-all-exercises/{user_id}",
524    operation_id = "getCourseInstanceExerciseStatusesForUser",
525    tag = "course-instances",
526    params(
527        ("course_instance_id" = Uuid, Path, description = "Course instance id"),
528        ("user_id" = Uuid, Path, description = "User id")
529    ),
530    responses(
531        (status = 200, description = "Exercise statuses for user", body = serde_json::Value)
532    )
533)]
534async fn get_all_exercise_statuses_by_course_instance_id(
535    params: web::Path<(Uuid, Uuid)>,
536    pool: web::Data<PgPool>,
537    user: AuthUser,
538) -> ControllerResult<web::Json<Vec<ExerciseStatusSummaryForUser>>> {
539    let (course_instance_id, user_id) = params.into_inner();
540    let mut conn = pool.acquire().await?;
541    let token = authorize(
542        &mut conn,
543        Act::ViewUserProgressOrDetails,
544        Some(user.id),
545        Res::CourseInstance(course_instance_id),
546    )
547    .await?;
548
549    let course_instance =
550        models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
551
552    let _enrollment = models::course_instance_enrollments::get_by_user_and_course_instance_id(
553        &mut conn,
554        user_id,
555        course_instance_id,
556    )
557    .await
558    .map_err(|err| match err.error_type() {
559        ModelErrorType::RecordNotFound | ModelErrorType::NotFound => controller_err!(
560            Forbidden,
561            "User is not enrolled in the requested course instance".to_string()
562        ),
563        _ => err.into(),
564    })?;
565
566    let res = models::exercises::get_all_exercise_statuses_by_user_id_and_course_id(
567        &mut conn,
568        course_instance.course_id,
569        user_id,
570    )
571    .await?;
572
573    token.authorized_ok(web::Json(res))
574}
575
576/**
577GET /course-instances/:id/course-module-completions/:user_id - Returns a list of all course module completions for a given user for this course instance.
578*/
579#[instrument(skip(pool))]
580#[utoipa::path(
581    get,
582    path = "/{course_instance_id}/course-module-completions/{user_id}",
583    operation_id = "getCourseInstanceCourseModuleCompletionsForUser",
584    tag = "course-instances",
585    params(
586        ("course_instance_id" = Uuid, Path, description = "Course instance id"),
587        ("user_id" = Uuid, Path, description = "User id")
588    ),
589    responses(
590        (status = 200, description = "Course module completions for user", body = serde_json::Value)
591    )
592)]
593async fn get_all_get_all_course_module_completions_for_user_by_course_instance_id(
594    params: web::Path<(Uuid, Uuid)>,
595    pool: web::Data<PgPool>,
596    user: AuthUser,
597) -> ControllerResult<web::Json<Vec<CourseModuleCompletion>>> {
598    let (course_instance_id, user_id) = params.into_inner();
599    let mut conn = pool.acquire().await?;
600    let token = authorize(
601        &mut conn,
602        Act::ViewUserProgressOrDetails,
603        Some(user.id),
604        Res::CourseInstance(course_instance_id),
605    )
606    .await?;
607
608    let course_instance =
609        models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
610
611    let _enrollment = models::course_instance_enrollments::get_by_user_and_course_instance_id(
612        &mut conn,
613        user_id,
614        course_instance_id,
615    )
616    .await
617    .map_err(|err| match err.error_type() {
618        ModelErrorType::RecordNotFound | ModelErrorType::NotFound => controller_err!(
619            Forbidden,
620            "User is not enrolled in the requested course instance".to_string()
621        ),
622        _ => err.into(),
623    })?;
624
625    let res = models::course_module_completions::get_all_by_course_id_and_user_id(
626        &mut conn,
627        course_instance.course_id,
628        user_id,
629    )
630    .await?;
631
632    token.authorized_ok(web::Json(res))
633}
634
635/**
636 GET /api/v0/main-frontend/course-instance/:course_instance_id/progress/:user_id - returns user progress information.
637*/
638#[instrument(skip(pool))]
639#[utoipa::path(
640    get,
641    path = "/{course_instance_id}/progress/{user_id}",
642    operation_id = "getCourseInstanceUserProgress",
643    tag = "course-instances",
644    params(
645        ("course_instance_id" = Uuid, Path, description = "Course instance id"),
646        ("user_id" = Uuid, Path, description = "User id")
647    ),
648    responses(
649        (status = 200, description = "User progress for course instance", body = serde_json::Value)
650    )
651)]
652async fn get_user_progress_for_course_instance(
653    user: AuthUser,
654    params: web::Path<(Uuid, Uuid)>,
655    pool: web::Data<PgPool>,
656) -> ControllerResult<web::Json<Vec<UserCourseProgress>>> {
657    let (course_instance_id, user_id) = params.into_inner();
658    let mut conn = pool.acquire().await?;
659    let token = authorize(
660        &mut conn,
661        Act::ViewUserProgressOrDetails,
662        Some(user.id),
663        Res::CourseInstance(course_instance_id),
664    )
665    .await?;
666
667    let course_instance =
668        models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
669
670    let _enrollment = models::course_instance_enrollments::get_by_user_and_course_instance_id(
671        &mut conn,
672        user_id,
673        course_instance_id,
674    )
675    .await
676    .map_err(|err| match err.error_type() {
677        ModelErrorType::RecordNotFound | ModelErrorType::NotFound => controller_err!(
678            Forbidden,
679            "User is not enrolled in the requested course instance".to_string()
680        ),
681        _ => err.into(),
682    })?;
683
684    let user_course_progress = models::user_exercise_states::get_user_course_progress(
685        &mut conn,
686        course_instance.course_id,
687        user_id,
688        false,
689    )
690    .await?;
691    token.authorized_ok(web::Json(user_course_progress))
692}
693
694/**
695Add a route for each controller in this module.
696
697The name starts with an underline in order to appear before other functions in the module documentation.
698
699We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
700*/
701pub fn _add_routes(cfg: &mut ServiceConfig) {
702    cfg.route("/{course_instance_id}", web::get().to(get_course_instance))
703        .route(
704            "/{course_instance_id}/email-templates",
705            web::post().to(post_new_email_template),
706        )
707        .route(
708            "/{course_instance_id}/email-templates",
709            web::get().to(get_email_templates_by_course_instance_id),
710        )
711        .route(
712            "/{course_instance_id}/export-points",
713            web::get().to(point_export),
714        )
715        .route("/{course_instance_id}/edit", web::post().to(edit))
716        .route("/{course_instance_id}/delete", web::post().to(delete))
717        .route(
718            "/{course_instance_id}/completions",
719            web::get().to(completions),
720        )
721        .route(
722            "/{course_instance_id}/export-completions",
723            web::get().to(completions_export),
724        )
725        .route(
726            "/{course_instance_id}/completions",
727            web::post().to(post_completions),
728        )
729        .route(
730            "/{course_instance_id}/completions/preview",
731            web::post().to(preview_post_completions),
732        )
733        .route("/{course_instance_id}/points", web::get().to(points))
734        .route(
735            "/{course_instance_id}/status-for-all-exercises/{user_id}",
736            web::get().to(get_all_exercise_statuses_by_course_instance_id),
737        )
738        .route(
739            "/{course_instance_id}/course-module-completions/{user_id}",
740            web::get().to(get_all_get_all_course_module_completions_for_user_by_course_instance_id),
741        )
742        .route(
743            "/{course_instance_id}/progress/{user_id}",
744            web::get().to(get_user_progress_for_course_instance),
745        )
746        .route(
747            "/{course_instance_id}/default-certificate-configurations",
748            web::get().to(certificate_configurations),
749        );
750}