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 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 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}