#
db_sqlx_maria/
lib.rs

    1// Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
    2// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
    3//
    4// SPDX-License-Identifier: AGPL-3.0-or-later
    5
    6use 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
    25/// Use an existing database pool
    26pub struct Conn(pub MySqlPool);
    27
    28/// Connect to database
    29pub enum ConnectionOptions {
    30    /// fresh connection
    31    Fresh(Fresh),
    32    /// existing connection
    33    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    /// ping DB
    93    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    /// register a new user
    104    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    /// delete a user
    132    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    /// check if username exists
    141    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    /// get user email
    153    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    /// check if email exists
    170    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    /// update a user's email
    182    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    /// get a user's password
    197    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    /// update user's password
    232    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    /// update username
    247    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    /// get a user's secret
    262    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    /// get a user's secret from a captcha key
    276    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    /// update a user's secret
    292    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    /// create new captcha
    307    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    /// Get captcha config
    325    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    /// Get all captchas belonging to user
    342    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    /// update captcha metadata; doesn't change captcha key
    361    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    /// update captcha key; doesn't change metadata
    383    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    /// Add levels to captcha
    404    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    /// check if captcha exists
    476    async fn captcha_exists(
    477        &self,
    478        username: Option<&str>,
    479        captcha_key: &str,
    480    ) -> DBResult<bool> {
    481        //        let mut exists = false;
    482
    483        #[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                //                if let Some(x) = x.exists {
    501                //                    exists = x;
    502                //                };
    503            }
    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            } //if let Some(x) = x.exists {
    514              //    exists = x;
    515              //};
    516        };
    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    /// Delete all levels of a captcha
    525    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    /// Delete captcha
    549    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    /// Get captcha levels
    565    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    /// Get captcha's cooldown period
    614    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    /// Add traffic configuration
    632    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            //payload.avg_traffic,
    649            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    /// Get traffic configuration
    662    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    /// Delete traffic configuration
    712    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    /// create new notification
    738    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    /// get all unread notifications
    763    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    /// mark a notification read
    786    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    /// record PoWConfig fetches
    801    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    /// record PoWConfig solves
    816    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    /// record PoWConfig confirms
    831    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    /// fetch PoWConfig fetches
    846    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    /// fetch PoWConfig solves
    872    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    /// fetch PoWConfig confirms
    896    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    /// record PoW timing
    921    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    /// fetch PoW analytics
    942    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    /// Create psuedo ID against campaign ID to publish analytics
    994    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    /// Get psuedo ID from campaign ID
    1018    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    /// Get campaign ID from psuedo ID
    1038    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    /// Get all psuedo IDs
    1101    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    /// Track maximum nonce received against captcha levels
    1124    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    /// Get maximum nonce tracked so far for captcha levels
    1156    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    /// Get number of analytics entries that are under a certain duration
    1223    async fn stats_get_num_logs_under_time(&self, duration: u32) -> DBResult<usize> {
    1224        struct Count {
    1225            count: Option<i64>,
    1226        }
    1227
    1228        //"SELECT COUNT(*) FROM (SELECT difficulty_factor FROM mcaptcha_pow_analytics WHERE time <= ?) as count",
    1229        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    /// Get the entry at a location in the list of analytics entires under a certain time limited
    1246    /// and sorted in ascending order
    1247    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    /// Get all easy captcha configurations on instance
    1278    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)]
    1357/// Represents notification
    1358pub struct InnerNotification {
    1359    /// receiver name  of the notification
    1360    pub name: String,
    1361    /// heading of the notification
    1362    pub heading: String,
    1363    /// message of the notification
    1364    pub message: String,
    1365    /// when notification was received
    1366    pub received: OffsetDateTime,
    1367    /// db assigned ID of the notification
    1368    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}