headless_lms_models/
code_giveaway_codes.rs

1use futures::Stream;
2use sqlx::{QueryBuilder, Row};
3
4use crate::prelude::*;
5
6#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
7#[cfg_attr(feature = "ts_rs", derive(TS))]
8pub struct CodeGiveawayCode {
9    pub id: Uuid,
10    pub created_at: DateTime<Utc>,
11    pub updated_at: DateTime<Utc>,
12    pub deleted_at: Option<DateTime<Utc>>,
13    pub code_giveaway_id: Uuid,
14    pub code_given_to_user_id: Option<Uuid>,
15    pub added_by_user_id: Uuid,
16    pub code: String,
17}
18
19pub async fn get_by_id(conn: &mut PgConnection, id: Uuid) -> ModelResult<CodeGiveawayCode> {
20    let res = sqlx::query_as!(
21        CodeGiveawayCode,
22        r#"
23SELECT *
24FROM code_giveaway_codes
25WHERE id = $1
26        "#,
27        id
28    )
29    .fetch_one(conn)
30    .await?;
31    Ok(res)
32}
33
34pub async fn insert_many(
35    conn: &mut PgConnection,
36    code_giveaway_id: Uuid,
37    input: &[String],
38    added_by_user_id: Uuid,
39) -> ModelResult<Vec<CodeGiveawayCode>> {
40    if input.is_empty() {
41        return Ok(vec![]);
42    }
43    let mut query_builder = QueryBuilder::new(
44        "INSERT INTO code_giveaway_codes (code_giveaway_id, code, added_by_user_id) ",
45    );
46
47    query_builder.push_values(input, |mut b, code| {
48        b.push_bind(code_giveaway_id)
49            .push_bind(code.trim())
50            .push_bind(added_by_user_id);
51    });
52
53    query_builder.push(" RETURNING id");
54
55    let query = query_builder.build();
56
57    let ids: Vec<Uuid> = query
58        .fetch_all(&mut *conn)
59        .await?
60        .iter()
61        .map(|row| row.get("id"))
62        .collect();
63
64    // Fetch the inserted rows to return them
65    let res = sqlx::query_as!(
66        CodeGiveawayCode,
67        r#"
68SELECT *
69FROM code_giveaway_codes
70WHERE code_giveaway_id = $1
71  AND added_by_user_id = $2
72  AND id = ANY($3)
73    "#,
74        code_giveaway_id,
75        added_by_user_id,
76        &ids
77    )
78    .fetch_all(&mut *conn)
79    .await?;
80
81    Ok(res)
82}
83
84pub async fn get_all_by_code_giveaway_id(
85    conn: &mut PgConnection,
86    code_giveaway_id: Uuid,
87) -> ModelResult<Vec<CodeGiveawayCode>> {
88    let res = sqlx::query_as!(
89        CodeGiveawayCode,
90        r#"
91SELECT *
92FROM code_giveaway_codes
93WHERE code_giveaway_id = $1
94  AND deleted_at IS NULL
95        "#,
96        code_giveaway_id
97    )
98    .fetch_all(conn)
99    .await?;
100    Ok(res)
101}
102
103pub async fn get_code_given_to_user(
104    conn: &mut PgConnection,
105    code_giveaway_id: Uuid,
106    user_id: Uuid,
107) -> ModelResult<Option<CodeGiveawayCode>> {
108    let res = sqlx::query_as!(
109        CodeGiveawayCode,
110        r#"
111SELECT *
112FROM code_giveaway_codes
113WHERE code_giveaway_id = $1
114  AND code_given_to_user_id = $2
115  AND deleted_at IS NULL
116        "#,
117        code_giveaway_id,
118        user_id
119    )
120    .fetch_optional(conn)
121    .await?;
122    Ok(res)
123}
124
125pub async fn give_some_code_to_user(
126    conn: &mut PgConnection,
127    code_giveaway_id: Uuid,
128    user_id: Uuid,
129) -> ModelResult<CodeGiveawayCode> {
130    let res = sqlx::query_as!(
131        CodeGiveawayCode,
132        r#"
133WITH to_update AS (
134    SELECT *
135    FROM code_giveaway_codes
136    WHERE code_giveaway_id = $1
137      AND code_given_to_user_id IS NULL
138      AND deleted_at IS NULL
139    LIMIT 1
140)
141UPDATE code_giveaway_codes cgc
142SET code_given_to_user_id = $2
143FROM to_update
144WHERE cgc.id = to_update.id
145RETURNING cgc.*
146        "#,
147        code_giveaway_id,
148        user_id
149    )
150    .fetch_one(conn)
151    .await?;
152    Ok(res)
153}
154
155#[allow(clippy::needless_lifetimes)]
156pub async fn stream_given_code_giveaway_codes<'a>(
157    conn: &'a mut PgConnection,
158    code_giveaway_id: Uuid,
159) -> impl Stream<Item = sqlx::Result<CodeGiveawayCode>> + 'a {
160    sqlx::query_as!(
161        CodeGiveawayCode,
162        r#"
163SELECT *
164FROM code_giveaway_codes
165WHERE code_giveaway_id = $1
166  AND deleted_at IS NULL
167  AND code_given_to_user_id IS NOT NULL
168            "#,
169        code_giveaway_id
170    )
171    .fetch(conn)
172}
173
174pub async fn delete_by_id(conn: &mut PgConnection, code_id: Uuid) -> ModelResult<CodeGiveawayCode> {
175    let res = sqlx::query_as!(
176        CodeGiveawayCode,
177        r#"
178UPDATE code_giveaway_codes
179SET deleted_at = now()
180WHERE id = $1
181RETURNING *
182        "#,
183        code_id
184    )
185    .fetch_one(conn)
186    .await?;
187    Ok(res)
188}
189
190pub async fn are_any_codes_left(
191    conn: &mut PgConnection,
192    code_giveaway_id: Uuid,
193) -> ModelResult<bool> {
194    let res = sqlx::query!(
195        r#"
196SELECT EXISTS(
197    SELECT 1
198    FROM code_giveaway_codes
199    WHERE code_giveaway_id = $1
200      AND code_given_to_user_id IS NULL
201      AND deleted_at IS NULL
202    LIMIT 1
203  )
204        "#,
205        code_giveaway_id
206    )
207    .fetch_one(conn)
208    .await?;
209    Ok(res.exists.unwrap())
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215    use crate::{code_giveaways::NewCodeGiveaway, test_helper::*};
216
217    #[tokio::test]
218    async fn test_insert_many_empty() {
219        insert_data!(:tx, :user, :org, :course);
220
221        let code_giveaway = crate::code_giveaways::insert(
222            tx.as_mut(),
223            &NewCodeGiveaway {
224                course_id: course,
225                name: "Test giveaway".to_string(),
226                course_module_id: None,
227                require_course_specific_consent_form_question_id: None,
228            },
229        )
230        .await
231        .unwrap();
232
233        let insert_result = insert_many(tx.as_mut(), code_giveaway.id, &[], user)
234            .await
235            .unwrap();
236
237        assert!(insert_result.is_empty());
238    }
239
240    #[tokio::test]
241    async fn test_insert_many_with_data() {
242        insert_data!(:tx, :user, :org, :course);
243
244        let code_giveaway = crate::code_giveaways::insert(
245            tx.as_mut(),
246            &NewCodeGiveaway {
247                course_id: course,
248                name: "Test giveaway".to_string(),
249                course_module_id: None,
250                require_course_specific_consent_form_question_id: None,
251            },
252        )
253        .await
254        .unwrap();
255
256        let codes = vec![
257            "code1".to_string(),
258            "code2".to_string(),
259            "code3".to_string(),
260        ];
261
262        let insert_result = insert_many(tx.as_mut(), code_giveaway.id, &codes, user)
263            .await
264            .unwrap();
265
266        assert_eq!(insert_result.len(), codes.len());
267        for code in &codes {
268            let found = insert_result.iter().find(|c| c.code == *code);
269            assert!(found.is_some());
270        }
271        // Double checking
272        let all_codes = get_all_by_code_giveaway_id(tx.as_mut(), code_giveaway.id)
273            .await
274            .unwrap();
275        assert_eq!(all_codes.len(), codes.len());
276        for code in &codes {
277            let found = all_codes.iter().find(|c| c.code == *code);
278            assert!(found.is_some());
279        }
280    }
281}