headless_lms_server/controllers/course_material/
code_giveaways.rs

1//! Controllers for requests starting with `/api/v0/course-material/code-giveaways`.
2
3use crate::{domain::authorization::skip_authorize, prelude::*};
4use models::code_giveaways::CodeGiveawayStatus;
5use utoipa::OpenApi;
6
7#[derive(OpenApi)]
8#[openapi(paths(get_giveaway_status, claim_code_from_code_giveaway))]
9pub(crate) struct CourseMaterialCodeGiveawaysApiDoc;
10
11/**
12 GET /api/v0/course-material/code-giveaways/:id/status - Returns information about a code giveaway.
13*/
14#[utoipa::path(
15    get,
16    path = "/{id}/status",
17    operation_id = "getCodeGiveawayStatus",
18    tag = "course-material-code-giveaways",
19    params(
20        ("id" = Uuid, Path, description = "Code giveaway id")
21    ),
22    responses(
23        (status = 200, description = "Code giveaway status", body = CodeGiveawayStatus)
24    )
25)]
26#[instrument(skip(pool))]
27async fn get_giveaway_status(
28    user: AuthUser,
29    code_giveaway_id: web::Path<Uuid>,
30    pool: web::Data<PgPool>,
31) -> ControllerResult<web::Json<CodeGiveawayStatus>> {
32    let mut conn = pool.acquire().await?;
33    let token = skip_authorize();
34    let res =
35        models::code_giveaways::get_code_giveaway_status(&mut conn, *code_giveaway_id, user.id)
36            .await?;
37    token.authorized_ok(web::Json(res))
38}
39
40/**
41 POST /api/v0/course-material/code-giveaways/:id/claim - Claim a code from a code giveaway. If user has not completed the course module that is a requirement for the code, returns an error.
42*/
43#[utoipa::path(
44    post,
45    path = "/{id}/claim",
46    operation_id = "claimCodeFromCodeGiveaway",
47    tag = "course-material-code-giveaways",
48    params(
49        ("id" = Uuid, Path, description = "Code giveaway id")
50    ),
51    responses(
52        (status = 200, description = "Claimed code", body = String)
53    )
54)]
55#[instrument(skip(pool))]
56async fn claim_code_from_code_giveaway(
57    user: AuthUser,
58    code_giveaway_id: web::Path<Uuid>,
59    pool: web::Data<PgPool>,
60) -> ControllerResult<web::Json<String>> {
61    let mut conn = pool.acquire().await?;
62    let token = skip_authorize();
63    let code_giveaway = models::code_giveaways::get_by_id(&mut conn, *code_giveaway_id).await?;
64    if !code_giveaway.enabled {
65        return Err(ControllerError::new(
66            ControllerErrorType::Forbidden,
67            "Code giveaway is not enabled.".to_string(),
68            None,
69        ));
70    }
71    if let Some(course_module_id) = code_giveaway.course_module_id {
72        let course_module_completions =
73            models::course_module_completions::get_all_by_user_id_and_course_module_id(
74                &mut conn,
75                user.id,
76                course_module_id,
77            )
78            .await?;
79
80        course_module_completions
81            .iter()
82            .find(|c| c.passed)
83            .ok_or_else(|| {
84                ControllerError::new(
85                    ControllerErrorType::BadRequest,
86                    "You have not completed the required course module.".to_string(),
87                    None,
88                )
89            })?;
90    } else {
91        return Err(ControllerError::new(
92            ControllerErrorType::BadRequest,
93            "The required course module has not been configured to this code giveaway.".to_string(),
94            None,
95        ));
96    }
97
98    if let Some(question_id) = code_giveaway.require_course_specific_consent_form_question_id {
99        let answers =
100            models::research_forms::get_all_research_form_answers_with_user_course_and_question_id(
101                &mut conn,
102                user.id,
103                code_giveaway.course_id,
104                question_id,
105            )
106            .await
107            .optional()?;
108        if let Some(answers) = answers {
109            let consented = answers.iter().any(|a| a.research_consent);
110            if !consented {
111                return Err(ControllerError::new(
112                    ControllerErrorType::BadRequest,
113                    "You're not eligible for the code.".to_string(),
114                    None,
115                ));
116            }
117        } else {
118            return Err(ControllerError::new(
119                ControllerErrorType::BadRequest,
120                "You have not completed the required research form.".to_string(),
121                None,
122            ));
123        }
124    }
125
126    let already_given_code =
127        models::code_giveaway_codes::get_code_given_to_user(&mut conn, *code_giveaway_id, user.id)
128            .await?
129            .map(|o| o.code);
130
131    if let Some(code) = already_given_code {
132        // This is for a pretty message, in the end a database constraint ensures that only one code can be given to a user.
133        return token.authorized_ok(web::Json(code));
134    }
135
136    let give_code_result =
137        models::code_giveaway_codes::give_some_code_to_user(&mut conn, *code_giveaway_id, user.id)
138            .await;
139
140    if let Err(_e) = &give_code_result {
141        let codes_left =
142            models::code_giveaway_codes::are_any_codes_left(&mut conn, *code_giveaway_id).await?;
143        if !codes_left {
144            return Err(ControllerError::new(
145                ControllerErrorType::BadRequest,
146                "The giveaway has ran out of codes.".to_string(),
147                None,
148            ));
149        }
150    }
151
152    let code = give_code_result?.code;
153    token.authorized_ok(web::Json(code))
154}
155
156pub fn _add_routes(cfg: &mut ServiceConfig) {
157    cfg.route("/{id}/status", web::get().to(get_giveaway_status))
158        .route("/{id}/claim", web::post().to(claim_code_from_code_giveaway));
159}