1use std::str::FromStr;
7
8use db_core::dev::*;
9
10use sqlx::mysql::MySqlPoolOptions;
11use sqlx::types::time::OffsetDateTime;
12use sqlx::ConnectOptions;
13use sqlx::MySqlPool;
14use uuid::Uuid;
15
16pub mod errors;
17#[cfg(test)]
18pub mod tests;
19
20#[derive(Clone)]
21pub struct Database {
22 pub pool: MySqlPool,
23}
24
25pub struct Conn(pub MySqlPool);
27
28pub enum ConnectionOptions {
30 Fresh(Fresh),
32 Existing(Conn),
34}
35
36pub struct Fresh {
37 pub pool_options: MySqlPoolOptions,
38 pub disable_logging: bool,
39 pub url: String,
40}
41
42pub mod dev {
43 pub use super::errors::*;
44 pub use super::Database;
45 pub use db_core::dev::*;
46 pub use sqlx::Error;
47}
48
49pub mod prelude {
50 pub use super::*;
51}
52
53#[async_trait]
54impl Connect for ConnectionOptions {
55 type Pool = Database;
56 async fn connect(self) -> DBResult<Self::Pool> {
57 let pool = match self {
58 Self::Fresh(fresh) => {
59 let mut connect_options =
60 sqlx::mysql::MySqlConnectOptions::from_str(&fresh.url).unwrap();
61 if fresh.disable_logging {
62 connect_options = connect_options.disable_statement_logging();
63 }
64 fresh
65 .pool_options
66 .connect_with(connect_options)
67 .await
68 .map_err(|e| DBError::DBError(Box::new(e)))?
69 }
70
71 Self::Existing(conn) => conn.0,
72 };
73 Ok(Database { pool })
74 }
75}
76
77use dev::*;
78
79#[async_trait]
80impl Migrate for Database {
81 async fn migrate(&self) -> DBResult<()> {
82 sqlx::migrate!("./migrations/")
83 .run(&self.pool)
84 .await
85 .map_err(|e| DBError::DBError(Box::new(e)))?;
86 Ok(())
87 }
88}
89
90#[async_trait]
91impl MCDatabase for Database {
92 async fn ping(&self) -> bool {
94 use sqlx::Connection;
95
96 if let Ok(mut con) = self.pool.acquire().await {
97 con.ping().await.is_ok()
98 } else {
99 false
100 }
101 }
102
103 async fn register(&self, p: &Register) -> DBResult<()> {
105 let res = if let Some(email) = &p.email {
106 sqlx::query!(
107 "insert into mcaptcha_users
108 (name , password, email, secret) values (?, ?, ?, ?)",
109 &p.username,
110 &p.hash,
111 &email,
112 &p.secret,
113 )
114 .execute(&self.pool)
115 .await
116 } else {
117 sqlx::query!(
118 "INSERT INTO mcaptcha_users
119 (name , password, secret) VALUES (?, ?, ?)",
120 &p.username,
121 &p.hash,
122 &p.secret,
123 )
124 .execute(&self.pool)
125 .await
126 };
127 res.map_err(map_register_err)?;
128 Ok(())
129 }
130
131 async fn delete_user(&self, username: &str) -> DBResult<()> {
133 sqlx::query!("DELETE FROM mcaptcha_users WHERE name = (?)", username)
134 .execute(&self.pool)
135 .await
136 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
137 Ok(())
138 }
139
140 async fn username_exists(&self, username: &str) -> DBResult<bool> {
142 match sqlx::query!("SELECT name from mcaptcha_users WHERE name = ?", username,)
143 .fetch_one(&self.pool)
144 .await
145 {
146 Ok(_) => Ok(true),
147 Err(sqlx::Error::RowNotFound) => Ok(false),
148 Err(e) => Err(map_register_err(e)),
149 }
150 }
151
152 async fn get_email(&self, username: &str) -> DBResult<Option<String>> {
154 struct Email {
155 email: Option<String>,
156 }
157
158 let res = sqlx::query_as!(
159 Email,
160 "SELECT email FROM mcaptcha_users WHERE name = ?",
161 username
162 )
163 .fetch_one(&self.pool)
164 .await
165 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
166 Ok(res.email)
167 }
168
169 async fn email_exists(&self, email: &str) -> DBResult<bool> {
171 match sqlx::query!("SELECT name from mcaptcha_users WHERE email = ?", email)
172 .fetch_one(&self.pool)
173 .await
174 {
175 Ok(_) => Ok(true),
176 Err(sqlx::Error::RowNotFound) => Ok(false),
177 Err(e) => Err(map_register_err(e)),
178 }
179 }
180
181 async fn update_email(&self, p: &UpdateEmail) -> DBResult<()> {
183 sqlx::query!(
184 "UPDATE mcaptcha_users set email = ?
185 WHERE name = ?",
186 &p.new_email,
187 &p.username,
188 )
189 .execute(&self.pool)
190 .await
191 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
192
193 Ok(())
194 }
195
196 async fn get_password(&self, l: &Login) -> DBResult<NameHash> {
198 struct Password {
199 name: String,
200 password: String,
201 }
202
203 let rec = match l {
204 Login::Username(u) => sqlx::query_as!(
205 Password,
206 r#"SELECT name, password FROM mcaptcha_users WHERE name = ?"#,
207 u,
208 )
209 .fetch_one(&self.pool)
210 .await
211 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?,
212
213 Login::Email(e) => sqlx::query_as!(
214 Password,
215 r#"SELECT name, password FROM mcaptcha_users WHERE email = ?"#,
216 e,
217 )
218 .fetch_one(&self.pool)
219 .await
220 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?,
221 };
222
223 let res = NameHash {
224 hash: rec.password,
225 username: rec.name,
226 };
227
228 Ok(res)
229 }
230
231 async fn update_password(&self, p: &NameHash) -> DBResult<()> {
233 sqlx::query!(
234 "UPDATE mcaptcha_users set password = ?
235 WHERE name = ?",
236 &p.hash,
237 &p.username,
238 )
239 .execute(&self.pool)
240 .await
241 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
242
243 Ok(())
244 }
245
246 async fn update_username(&self, current: &str, new: &str) -> DBResult<()> {
248 sqlx::query!(
249 "UPDATE mcaptcha_users set name = ?
250 WHERE name = ?",
251 new,
252 current,
253 )
254 .execute(&self.pool)
255 .await
256 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
257
258 Ok(())
259 }
260
261 async fn get_secret(&self, username: &str) -> DBResult<Secret> {
263 let secret = sqlx::query_as!(
264 Secret,
265 r#"SELECT secret FROM mcaptcha_users WHERE name = ?"#,
266 username,
267 )
268 .fetch_one(&self.pool)
269 .await
270 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
271
272 Ok(secret)
273 }
274
275 async fn get_secret_from_captcha(&self, key: &str) -> DBResult<Secret> {
277 let secret = sqlx::query_as!(
278 Secret,
279 r#"SELECT secret FROM mcaptcha_users WHERE ID = (
280 SELECT user_id FROM mcaptcha_config WHERE captcha_key = ?
281 )"#,
282 key,
283 )
284 .fetch_one(&self.pool)
285 .await
286 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
287
288 Ok(secret)
289 }
290
291 async fn update_secret(&self, username: &str, secret: &str) -> DBResult<()> {
293 sqlx::query!(
294 "UPDATE mcaptcha_users set secret = ?
295 WHERE name = ?",
296 &secret,
297 &username,
298 )
299 .execute(&self.pool)
300 .await
301 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
302
303 Ok(())
304 }
305
306 async fn create_captcha(&self, username: &str, p: &CreateCaptcha) -> DBResult<()> {
308 sqlx::query!(
309 "INSERT INTO mcaptcha_config
310 (`captcha_key`, `user_id`, `duration`, `name`)
311 VALUES (?, (SELECT ID FROM mcaptcha_users WHERE name = ?), ?, ?)",
312 p.key,
313 username,
314 p.duration as i32,
315 p.description,
316 )
317 .execute(&self.pool)
318 .await
319 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
320
321 Ok(())
322 }
323
324 async fn get_captcha_config(&self, username: &str, key: &str) -> DBResult<Captcha> {
326 let captcha = sqlx::query_as!(
327 InternaleCaptchaConfig,
328 "SELECT `config_id`, `duration`, `name`, `captcha_key` from mcaptcha_config WHERE
329 `captcha_key` = ? AND
330 user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?) ",
331 &key,
332 &username,
333 )
334 .fetch_one(&self.pool)
335 .await
336 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
337
338 Ok(captcha.into())
339 }
340
341 async fn get_all_user_captchas(&self, username: &str) -> DBResult<Vec<Captcha>> {
343 let mut res = sqlx::query_as!(
344 InternaleCaptchaConfig,
345 "SELECT captcha_key, name, config_id, duration FROM mcaptcha_config WHERE
346 user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?) ",
347 &username,
348 )
349 .fetch_all(&self.pool)
350 .await
351 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
352
353 let mut captchas = Vec::with_capacity(res.len());
354
355 res.drain(0..).for_each(|r| captchas.push(r.into()));
356
357 Ok(captchas)
358 }
359
360 async fn update_captcha_metadata(
362 &self,
363 username: &str,
364 p: &CreateCaptcha,
365 ) -> DBResult<()> {
366 sqlx::query!(
367 "UPDATE mcaptcha_config SET name = ?, duration = ?
368 WHERE user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)
369 AND captcha_key = ?",
370 p.description,
371 p.duration,
372 username,
373 p.key,
374 )
375 .execute(&self.pool)
376 .await
377 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
378
379 Ok(())
380 }
381
382 async fn update_captcha_key(
384 &self,
385 username: &str,
386 old_key: &str,
387 new_key: &str,
388 ) -> DBResult<()> {
389 sqlx::query!(
390 "UPDATE mcaptcha_config SET captcha_key = ?
391 WHERE captcha_key = ? AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)",
392 new_key,
393 old_key,
394 username,
395 )
396 .execute(&self.pool)
397 .await
398 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
399
400 Ok(())
401 }
402
403 async fn add_captcha_levels(
405 &self,
406 username: &str,
407 captcha_key: &str,
408 levels: &[Level],
409 ) -> DBResult<()> {
410 use futures::future::try_join_all;
411 let mut futs = Vec::with_capacity(levels.len());
412
413 for level in levels.iter() {
414 let difficulty_factor = level.difficulty_factor as i32;
415 let visitor_threshold = level.visitor_threshold as i32;
416 let fut = sqlx::query!(
417 "INSERT INTO mcaptcha_levels (
418 difficulty_factor,
419 visitor_threshold,
420 config_id) VALUES (
421 ?, ?, (
422 SELECT config_id FROM mcaptcha_config WHERE
423 captcha_key = (?) AND user_id = (
424 SELECT ID FROM mcaptcha_users WHERE name = ?
425 )));",
426 difficulty_factor,
427 visitor_threshold,
428 &captcha_key,
429 username,
430 )
431 .execute(&self.pool);
432 futs.push(fut);
433 }
434
435 try_join_all(futs)
436 .await
437 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
438
439 let mut futs = Vec::with_capacity(levels.len());
440
441 for level in levels.iter() {
442 let difficulty_factor = level.difficulty_factor as i32;
443 let visitor_threshold = level.visitor_threshold as i32;
444 let fut = sqlx::query!(
445 "INSERT INTO
446 mcaptcha_track_nonce (level_id, nonce)
447 VALUES ((
448 SELECT
449 level_id
450 FROM
451 mcaptcha_levels
452 WHERE
453 config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)
454 AND
455 difficulty_factor = ?
456 AND
457 visitor_threshold = ?
458 ), ?);",
459 &captcha_key,
460 difficulty_factor,
461 visitor_threshold,
462 0,
463 )
464 .execute(&self.pool);
465 futs.push(fut);
466 }
467
468 try_join_all(futs)
469 .await
470 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
471
472 Ok(())
473 }
474
475 async fn captcha_exists(
477 &self,
478 username: Option<&str>,
479 captcha_key: &str,
480 ) -> DBResult<bool> {
481 #[allow(dead_code)]
484 struct ConfigId {
485 config_id: i32,
486 }
487 let res = match username {
488 Some(username) => {
489 sqlx::query_as!(
490 ConfigId,
491 "SELECT config_id FROM mcaptcha_config
492 WHERE
493 captcha_key = ?
494 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)",
495 captcha_key,
496 username
497 )
498 .fetch_one(&self.pool)
499 .await
500 }
504
505 None => {
506 sqlx::query_as!(
507 ConfigId,
508 "SELECT config_id from mcaptcha_config WHERE captcha_key = ?",
509 &captcha_key,
510 )
511 .fetch_one(&self.pool)
512 .await
513 } };
517 match res {
518 Ok(_) => Ok(true),
519 Err(sqlx::Error::RowNotFound) => Ok(false),
520 Err(e) => Err(map_register_err(e)),
521 }
522 }
523
524 async fn delete_captcha_levels(
526 &self,
527 username: &str,
528 captcha_key: &str,
529 ) -> DBResult<()> {
530 sqlx::query!(
531 "DELETE FROM mcaptcha_levels
532 WHERE config_id = (
533 SELECT config_id FROM mcaptcha_config where captcha_key= (?)
534 AND user_id = (
535 SELECT ID from mcaptcha_users WHERE name = ?
536 )
537 )",
538 captcha_key,
539 username
540 )
541 .execute(&self.pool)
542 .await
543 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
544
545 Ok(())
546 }
547
548 async fn delete_captcha(&self, username: &str, captcha_key: &str) -> DBResult<()> {
550 sqlx::query!(
551 "DELETE FROM mcaptcha_config where captcha_key= (?)
552 AND
553 user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)",
554 captcha_key,
555 username,
556 )
557 .execute(&self.pool)
558 .await
559 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
560
561 Ok(())
562 }
563
564 async fn get_captcha_levels(
566 &self,
567 username: Option<&str>,
568 captcha_key: &str,
569 ) -> DBResult<Vec<Level>> {
570 struct I32Levels {
571 difficulty_factor: i32,
572 visitor_threshold: i32,
573 }
574 let levels = match username {
575 None => sqlx::query_as!(
576 I32Levels,
577 "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE
578 config_id = (
579 SELECT config_id FROM mcaptcha_config where captcha_key= (?)
580 ) ORDER BY difficulty_factor ASC;",
581 captcha_key,
582 )
583 .fetch_all(&self.pool)
584 .await
585 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?,
586
587 Some(username) => sqlx::query_as!(
588 I32Levels,
589 "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE
590 config_id = (
591 SELECT config_id FROM mcaptcha_config where captcha_key= (?)
592 AND user_id = (SELECT ID from mcaptcha_users WHERE name = ?)
593 )
594 ORDER BY difficulty_factor ASC;",
595 captcha_key,
596 username
597 )
598 .fetch_all(&self.pool)
599 .await
600 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?,
601 };
602
603 let mut new_levels = Vec::with_capacity(levels.len());
604 for l in levels.iter() {
605 new_levels.push(Level {
606 difficulty_factor: l.difficulty_factor as u32,
607 visitor_threshold: l.visitor_threshold as u32,
608 });
609 }
610 Ok(new_levels)
611 }
612
613 async fn get_captcha_cooldown(&self, captcha_key: &str) -> DBResult<i32> {
615 struct DurationResp {
616 duration: i32,
617 }
618
619 let resp = sqlx::query_as!(
620 DurationResp,
621 "SELECT duration FROM mcaptcha_config
622 where captcha_key= ?",
623 captcha_key,
624 )
625 .fetch_one(&self.pool)
626 .await
627 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
628
629 Ok(resp.duration)
630 }
631 async fn add_traffic_pattern(
633 &self,
634 username: &str,
635 captcha_key: &str,
636 pattern: &TrafficPattern,
637 ) -> DBResult<()> {
638 sqlx::query!(
639 "INSERT INTO mcaptcha_sitekey_user_provided_avg_traffic (
640 config_id,
641 avg_traffic,
642 peak_sustainable_traffic,
643 broke_my_site_traffic
644 ) VALUES (
645 (SELECT config_id FROM mcaptcha_config where captcha_key= (?)
646 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)
647 ), ?, ?, ?)",
648 captcha_key,
650 username,
651 pattern.avg_traffic as i32,
652 pattern.peak_sustainable_traffic as i32,
653 pattern.broke_my_site_traffic.as_ref().map(|v| *v as i32),
654 )
655 .execute(&self.pool)
656 .await
657 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
658 Ok(())
659 }
660
661 async fn get_traffic_pattern(
663 &self,
664 username: &str,
665 captcha_key: &str,
666 ) -> DBResult<TrafficPattern> {
667 struct Traffic {
668 peak_sustainable_traffic: i32,
669 avg_traffic: i32,
670 broke_my_site_traffic: Option<i32>,
671 }
672 let res = sqlx::query_as!(
673 Traffic,
674 "SELECT
675 avg_traffic,
676 peak_sustainable_traffic,
677 broke_my_site_traffic
678 FROM
679 mcaptcha_sitekey_user_provided_avg_traffic
680 WHERE
681 config_id = (
682 SELECT
683 config_id
684 FROM
685 mcaptcha_config
686 WHERE
687 captcha_key = ?
688 AND user_id = (
689 SELECT
690 id
691 FROM
692 mcaptcha_users
693 WHERE
694 NAME = ?
695 )
696 )
697 ",
698 captcha_key,
699 username
700 )
701 .fetch_one(&self.pool)
702 .await
703 .map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
704 Ok(TrafficPattern {
705 broke_my_site_traffic: res.broke_my_site_traffic.as_ref().map(|v| *v as u32),
706 avg_traffic: res.avg_traffic as u32,
707 peak_sustainable_traffic: res.peak_sustainable_traffic as u32,
708 })
709 }
710
711 async fn delete_traffic_pattern(
713 &self,
714 username: &str,
715 captcha_key: &str,
716 ) -> DBResult<()> {
717 sqlx::query!(
718 "DELETE FROM mcaptcha_sitekey_user_provided_avg_traffic
719 WHERE config_id = (
720 SELECT config_id
721 FROM
722 mcaptcha_config
723 WHERE
724 captcha_key = ?
725 AND
726 user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)
727 );",
728 captcha_key,
729 username,
730 )
731 .execute(&self.pool)
732 .await
733 .map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
734 Ok(())
735 }
736
737 async fn create_notification(&self, p: &AddNotification) -> DBResult<()> {
739 let now = now_unix_time_stamp();
740 sqlx::query!(
741 "INSERT INTO mcaptcha_notifications (
742 heading, message, tx, rx, received)
743 VALUES (
744 ?, ?,
745 (SELECT ID FROM mcaptcha_users WHERE name = ?),
746 (SELECT ID FROM mcaptcha_users WHERE name = ?),
747 ?
748 );",
749 p.heading,
750 p.message,
751 p.from,
752 p.to,
753 now
754 )
755 .execute(&self.pool)
756 .await
757 .map_err(map_register_err)?;
758
759 Ok(())
760 }
761
762 async fn get_all_unread_notifications(
764 &self,
765 username: &str,
766 ) -> DBResult<Vec<Notification>> {
767 let mut inner_notifications = sqlx::query_file_as!(
768 InnerNotification,
769 "./src/get_all_unread_notifications.sql",
770 &username
771 )
772 .fetch_all(&self.pool)
773 .await
774 .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
775
776 let mut notifications = Vec::with_capacity(inner_notifications.len());
777
778 inner_notifications
779 .drain(0..)
780 .for_each(|n| notifications.push(n.into()));
781
782 Ok(notifications)
783 }
784
785 async fn mark_notification_read(&self, username: &str, id: i32) -> DBResult<()> {
787 sqlx::query_file_as!(
788 Notification,
789 "./src/mark_notification_read.sql",
790 id,
791 &username
792 )
793 .execute(&self.pool)
794 .await
795 .map_err(|e| map_row_not_found_err(e, DBError::NotificationNotFound))?;
796
797 Ok(())
798 }
799
800 async fn record_fetch(&self, key: &str) -> DBResult<()> {
802 let now = now_unix_time_stamp();
803 let _ = sqlx::query!(
804 "INSERT INTO mcaptcha_pow_fetched_stats
805 (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)",
806 key,
807 &now,
808 )
809 .execute(&self.pool)
810 .await
811 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
812 Ok(())
813 }
814
815 async fn record_solve(&self, key: &str) -> DBResult<()> {
817 let now = OffsetDateTime::now_utc();
818 let _ = sqlx::query!(
819 "INSERT INTO mcaptcha_pow_solved_stats
820 (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)",
821 key,
822 &now,
823 )
824 .execute(&self.pool)
825 .await
826 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
827 Ok(())
828 }
829
830 async fn record_confirm(&self, key: &str) -> DBResult<()> {
832 let now = now_unix_time_stamp();
833 let _ = sqlx::query!(
834 "INSERT INTO mcaptcha_pow_confirmed_stats
835 (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)",
836 key,
837 &now
838 )
839 .execute(&self.pool)
840 .await
841 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
842 Ok(())
843 }
844
845 async fn fetch_config_fetched(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
847 let records = sqlx::query_as!(
848 Date,
849 "SELECT time FROM mcaptcha_pow_fetched_stats
850 WHERE
851 config_id = (
852 SELECT
853 config_id FROM mcaptcha_config
854 WHERE
855 captcha_key = ?
856 AND
857 user_id = (
858 SELECT
859 ID FROM mcaptcha_users WHERE name = ?))
860 ORDER BY time DESC",
861 &key,
862 &user,
863 )
864 .fetch_all(&self.pool)
865 .await
866 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
867
868 Ok(Date::dates_to_unix(records))
869 }
870
871 async fn fetch_solve(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
873 let records = sqlx::query_as!(
874 Date,
875 "SELECT time FROM mcaptcha_pow_solved_stats
876 WHERE config_id = (
877 SELECT config_id FROM mcaptcha_config
878 WHERE
879 captcha_key = ?
880 AND
881 user_id = (
882 SELECT
883 ID FROM mcaptcha_users WHERE name = ?))
884 ORDER BY time DESC",
885 &key,
886 &user
887 )
888 .fetch_all(&self.pool)
889 .await
890 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
891
892 Ok(Date::dates_to_unix(records))
893 }
894
895 async fn fetch_confirm(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
897 let records = sqlx::query_as!(
898 Date,
899 "SELECT time FROM mcaptcha_pow_confirmed_stats
900 WHERE
901 config_id = (
902 SELECT config_id FROM mcaptcha_config
903 WHERE
904 captcha_key = ?
905 AND
906 user_id = (
907 SELECT
908 ID FROM mcaptcha_users WHERE name = ?))
909 ORDER BY time DESC",
910 &key,
911 &user
912 )
913 .fetch_all(&self.pool)
914 .await
915 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
916
917 Ok(Date::dates_to_unix(records))
918 }
919
920 async fn analysis_save(
922 &self,
923 captcha_id: &str,
924 d: &CreatePerformanceAnalytics,
925 ) -> DBResult<()> {
926 let _ = sqlx::query!(
927 "INSERT INTO mcaptcha_pow_analytics
928 (config_id, time, difficulty_factor, worker_type)
929 VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?, ?, ?)",
930 captcha_id,
931 d.time as i32,
932 d.difficulty_factor as i32,
933 &d.worker_type,
934 )
935 .execute(&self.pool)
936 .await
937 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
938 Ok(())
939 }
940
941 async fn analytics_fetch(
943 &self,
944 captcha_id: &str,
945 limit: usize,
946 offset: usize,
947 ) -> DBResult<Vec<PerformanceAnalytics>> {
948 struct P {
949 id: i32,
950 time: i32,
951 difficulty_factor: i32,
952 worker_type: String,
953 }
954
955 impl From<P> for PerformanceAnalytics {
956 fn from(v: P) -> Self {
957 Self {
958 id: v.id as usize,
959 time: v.time as u32,
960 difficulty_factor: v.difficulty_factor as u32,
961 worker_type: v.worker_type,
962 }
963 }
964 }
965
966 let mut c = sqlx::query_as!(
967 P,
968 "SELECT
969 id, time, difficulty_factor, worker_type
970 FROM
971 mcaptcha_pow_analytics
972 WHERE
973 config_id = (
974 SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?
975 )
976 ORDER BY ID
977 LIMIT ? OFFSET ?",
978 &captcha_id,
979 limit as i64,
980 offset as i64,
981 )
982 .fetch_all(&self.pool)
983 .await
984 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
985 let mut res = Vec::with_capacity(c.len());
986 for i in c.drain(0..) {
987 res.push(i.into())
988 }
989
990 Ok(res)
991 }
992
993 async fn analytics_create_psuedo_id_if_not_exists(
995 &self,
996 captcha_id: &str,
997 ) -> DBResult<()> {
998 let id = Uuid::new_v4();
999 sqlx::query!(
1000 "
1001 INSERT INTO
1002 mcaptcha_psuedo_campaign_id (config_id, psuedo_id)
1003 VALUES (
1004 (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?)),
1005 ?
1006 );",
1007 captcha_id,
1008 &id.to_string(),
1009 )
1010 .execute(&self.pool)
1011 .await
1012 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
1013
1014 Ok(())
1015 }
1016
1017 async fn analytics_get_psuedo_id_from_capmaign_id(
1019 &self,
1020 captcha_id: &str,
1021 ) -> DBResult<String> {
1022 let res = sqlx::query_as!(
1023 PsuedoID,
1024 "SELECT psuedo_id FROM
1025 mcaptcha_psuedo_campaign_id
1026 WHERE
1027 config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?));
1028 ",
1029 captcha_id
1030 ).fetch_one(&self.pool)
1031 .await
1032 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
1033
1034 Ok(res.psuedo_id)
1035 }
1036
1037 async fn analytics_get_capmaign_id_from_psuedo_id(
1039 &self,
1040 psuedo_id: &str,
1041 ) -> DBResult<String> {
1042 struct ID {
1043 captcha_key: String,
1044 }
1045
1046 let res = sqlx::query_as!(
1047 ID,
1048 "SELECT
1049 captcha_key
1050 FROM
1051 mcaptcha_config
1052 WHERE
1053 config_id = (
1054 SELECT
1055 config_id
1056 FROM
1057 mcaptcha_psuedo_campaign_id
1058 WHERE
1059 psuedo_id = ?
1060 );",
1061 psuedo_id
1062 )
1063 .fetch_one(&self.pool)
1064 .await
1065 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
1066 Ok(res.captcha_key)
1067 }
1068
1069 async fn analytics_delete_all_records_for_campaign(
1070 &self,
1071 campaign_id: &str,
1072 ) -> DBResult<()> {
1073 let _ = sqlx::query!(
1074 "
1075 DELETE FROM
1076 mcaptcha_psuedo_campaign_id
1077 WHERE config_id = (
1078 SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?
1079 );",
1080 campaign_id
1081 )
1082 .execute(&self.pool)
1083 .await;
1084
1085 let _ = sqlx::query!(
1086 "
1087 DELETE FROM
1088 mcaptcha_pow_analytics
1089 WHERE
1090 config_id = (
1091 SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?
1092 ) ",
1093 campaign_id
1094 )
1095 .execute(&self.pool)
1096 .await;
1097
1098 Ok(())
1099 }
1100 async fn analytics_get_all_psuedo_ids(&self, page: usize) -> DBResult<Vec<String>> {
1102 const LIMIT: usize = 50;
1103 let offset = LIMIT * page;
1104
1105 let mut res = sqlx::query_as!(
1106 PsuedoID,
1107 "
1108 SELECT
1109 psuedo_id
1110 FROM
1111 mcaptcha_psuedo_campaign_id
1112 ORDER BY ID ASC LIMIT ? OFFSET ?;",
1113 LIMIT as i64,
1114 offset as i64
1115 )
1116 .fetch_all(&self.pool)
1117 .await
1118 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
1119
1120 Ok(res.drain(0..).map(|r| r.psuedo_id).collect())
1121 }
1122
1123 async fn update_max_nonce_for_level(
1125 &self,
1126 captcha_key: &str,
1127 difficulty_factor: u32,
1128 latest_nonce: u32,
1129 ) -> DBResult<()> {
1130 let latest_nonce = latest_nonce as i64;
1131 sqlx::query!(
1132 "UPDATE mcaptcha_track_nonce SET nonce = ?
1133 WHERE level_id = (
1134 SELECT
1135 level_id
1136 FROM
1137 mcaptcha_levels
1138 WHERE
1139 config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)
1140 AND
1141 difficulty_factor = ?
1142 )
1143 AND nonce <= ?;",
1144 latest_nonce,
1145 &captcha_key,
1146 difficulty_factor as i64,
1147 latest_nonce
1148 )
1149 .execute(&self.pool).await
1150 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
1151
1152 Ok(())
1153 }
1154
1155 async fn get_max_nonce_for_level(
1157 &self,
1158 captcha_key: &str,
1159 difficulty_factor: u32,
1160 ) -> DBResult<u32> {
1161 struct X {
1162 nonce: i32,
1163 }
1164
1165 async fn inner_get_max_nonce(
1166 pool: &MySqlPool,
1167 captcha_key: &str,
1168 difficulty_factor: u32,
1169 ) -> DBResult<X> {
1170 sqlx::query_as!(
1171 X,
1172 "SELECT nonce FROM mcaptcha_track_nonce
1173 WHERE level_id = (
1174 SELECT
1175 level_id
1176 FROM
1177 mcaptcha_levels
1178 WHERE
1179 config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)
1180 AND
1181 difficulty_factor = ?
1182 );",
1183 &captcha_key,
1184 difficulty_factor as i32,
1185 )
1186 .fetch_one(pool).await
1187 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))
1188 }
1189
1190 let res = inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await;
1191 if let Err(DBError::CaptchaNotFound) = res {
1192 sqlx::query!(
1193 "INSERT INTO
1194 mcaptcha_track_nonce (level_id, nonce)
1195 VALUES ((
1196 SELECT
1197 level_id
1198 FROM
1199 mcaptcha_levels
1200 WHERE
1201 config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key =?)
1202 AND
1203 difficulty_factor = ?
1204 ), ?);",
1205 &captcha_key,
1206 difficulty_factor as i32,
1207 0,
1208 )
1209 .execute(&self.pool)
1210 .await
1211 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
1212
1213 let res =
1214 inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await?;
1215 Ok(res.nonce as u32)
1216 } else {
1217 let res = res?;
1218 Ok(res.nonce as u32)
1219 }
1220 }
1221
1222 async fn stats_get_num_logs_under_time(&self, duration: u32) -> DBResult<usize> {
1224 struct Count {
1225 count: Option<i64>,
1226 }
1227
1228 let count = sqlx::query_as!(
1230 Count,
1231 "SELECT
1232 COUNT(difficulty_factor) AS count
1233 FROM
1234 mcaptcha_pow_analytics
1235 WHERE time <= ?;",
1236 duration as i32,
1237 )
1238 .fetch_one(&self.pool)
1239 .await
1240 .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
1241
1242 Ok(count.count.unwrap_or_else(|| 0) as usize)
1243 }
1244
1245 async fn stats_get_entry_at_location_for_time_limit_asc(
1248 &self,
1249 duration: u32,
1250 location: u32,
1251 ) -> DBResult<Option<usize>> {
1252 struct Difficulty {
1253 difficulty_factor: Option<i32>,
1254 }
1255
1256 match sqlx::query_as!(
1257 Difficulty,
1258 "SELECT
1259 difficulty_factor
1260 FROM
1261 mcaptcha_pow_analytics
1262 WHERE
1263 time <= ?
1264 ORDER BY difficulty_factor ASC LIMIT 1 OFFSET ?;",
1265 duration as i32,
1266 location as i64 - 1,
1267 )
1268 .fetch_one(&self.pool)
1269 .await
1270 {
1271 Ok(res) => Ok(Some(res.difficulty_factor.unwrap() as usize)),
1272 Err(sqlx::Error::RowNotFound) => Ok(None),
1273 Err(e) => Err(map_row_not_found_err(e, DBError::CaptchaNotFound)),
1274 }
1275 }
1276
1277 async fn get_all_easy_captchas(
1279 &self,
1280 limit: usize,
1281 offset: usize,
1282 ) -> DBResult<Vec<EasyCaptcha>> {
1283 struct InnerEasyCaptcha {
1284 captcha_key: String,
1285 name: String,
1286 username: String,
1287 peak_sustainable_traffic: i32,
1288 avg_traffic: i32,
1289 broke_my_site_traffic: Option<i32>,
1290 }
1291 let mut inner_res = sqlx::query_as!(
1292 InnerEasyCaptcha,
1293 "SELECT
1294 mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic,
1295 mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic,
1296 mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,
1297 mcaptcha_config.name,
1298 mcaptcha_users.name as username,
1299 mcaptcha_config.captcha_key
1300 FROM
1301 mcaptcha_sitekey_user_provided_avg_traffic
1302 INNER JOIN
1303 mcaptcha_config
1304 ON
1305 mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id
1306 INNER JOIN
1307 mcaptcha_users
1308 ON
1309 mcaptcha_config.user_id = mcaptcha_users.ID
1310 ORDER BY mcaptcha_config.config_id
1311 LIMIT ? OFFSET ?",
1312 limit as i64,
1313 offset as i64
1314 )
1315 .fetch_all(&self.pool)
1316 .await
1317 .map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
1318 let mut res = Vec::with_capacity(inner_res.len());
1319 inner_res.drain(0..).for_each(|v| {
1320 res.push(EasyCaptcha {
1321 key: v.captcha_key,
1322 description: v.name,
1323 username: v.username,
1324 traffic_pattern: TrafficPattern {
1325 broke_my_site_traffic: v
1326 .broke_my_site_traffic
1327 .as_ref()
1328 .map(|v| *v as u32),
1329 avg_traffic: v.avg_traffic as u32,
1330 peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
1331 },
1332 })
1333 });
1334 Ok(res)
1335 }
1336}
1337
1338#[derive(Clone)]
1339struct Date {
1340 time: OffsetDateTime,
1341}
1342
1343impl Date {
1344 fn dates_to_unix(mut d: Vec<Self>) -> Vec<i64> {
1345 let mut dates = Vec::with_capacity(d.len());
1346 d.drain(0..)
1347 .for_each(|x| dates.push(x.time.unix_timestamp()));
1348 dates
1349 }
1350}
1351
1352fn now_unix_time_stamp() -> OffsetDateTime {
1353 OffsetDateTime::now_utc()
1354}
1355
1356#[derive(Debug, Clone, PartialEq)]
1357pub struct InnerNotification {
1359 pub name: String,
1361 pub heading: String,
1363 pub message: String,
1365 pub received: OffsetDateTime,
1367 pub id: i32,
1369}
1370
1371impl From<InnerNotification> for Notification {
1372 fn from(n: InnerNotification) -> Self {
1373 Notification {
1374 name: Some(n.name),
1375 heading: Some(n.heading),
1376 message: Some(n.message),
1377 received: Some(n.received.unix_timestamp()),
1378 id: Some(n.id),
1379 }
1380 }
1381}
1382
1383#[derive(Clone)]
1384struct InternaleCaptchaConfig {
1385 config_id: i32,
1386 duration: i32,
1387 name: String,
1388 captcha_key: String,
1389}
1390
1391impl From<InternaleCaptchaConfig> for Captcha {
1392 fn from(i: InternaleCaptchaConfig) -> Self {
1393 Self {
1394 config_id: i.config_id,
1395 duration: i.duration,
1396 description: i.name,
1397 key: i.captcha_key,
1398 }
1399 }
1400}
1401
1402struct PsuedoID {
1403 psuedo_id: String,
1404}