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