tetratto-core 18.0.2

The core behind Tetratto
Documentation
use oiseau::cache::Cache;
use crate::model::apps::{AppDataQuery, AppDataQueryResult, AppDataSelectMode};
use crate::model::{apps::AppData, permissions::FinePermission, Error, Result};
use crate::{auto_method, DataManager};
use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};

pub const FREE_DATA_LIMIT: usize = 512_000;
pub const PASS_DATA_LIMIT: usize = 26_214_400;

impl DataManager {
    /// Get a [`AppData`] from an SQL row.
    pub(crate) fn get_app_data_from_row(x: &PostgresRow) -> AppData {
        AppData {
            id: get!(x->0(i64)) as usize,
            app: get!(x->1(i64)) as usize,
            key: get!(x->2(String)),
            value: get!(x->3(String)),
        }
    }

    auto_method!(get_app_data_by_id(usize as i64)@get_app_data_from_row -> "SELECT * FROM app_data WHERE id = $1" --name="app_data" --returns=AppData --cache-key-tmpl="atto.app_data:{}");

    /// Get all app_data by app.
    ///
    /// # Arguments
    /// * `id` - the ID of the app to fetch app_data for
    pub async fn get_app_data_by_app(&self, id: usize) -> Result<Vec<AppData>> {
        let conn = match self.0.connect().await {
            Ok(c) => c,
            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
        };

        let res = query_rows!(
            &conn,
            "SELECT * FROM app_data WHERE app = $1 ORDER BY created DESC",
            &[&(id as i64)],
            |x| { Self::get_app_data_from_row(x) }
        );

        if res.is_err() {
            return Err(Error::GeneralNotFound("app_data".to_string()));
        }

        Ok(res.unwrap())
    }

    /// Get all app_data by the given query.
    pub async fn query_app_data(&self, query: AppDataQuery) -> Result<AppDataQueryResult> {
        let conn = match self.0.connect().await {
            Ok(c) => c,
            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
        };

        let query_str = query.to_string().replace("%q%", &query.query.selector());

        let res = match query.mode {
            AppDataSelectMode::One(_) => AppDataQueryResult::One(
                match query_row!(&conn, &query_str, params![&query.query.to_string()], |x| {
                    Ok(Self::get_app_data_from_row(x))
                }) {
                    Ok(x) => x,
                    Err(_) => return Err(Error::GeneralNotFound("app_data".to_string())),
                },
            ),
            AppDataSelectMode::Many(_, _) => AppDataQueryResult::Many(
                match query_rows!(&conn, &query_str, params![&query.query.to_string()], |x| {
                    Self::get_app_data_from_row(x)
                }) {
                    Ok(x) => x,
                    Err(_) => return Err(Error::GeneralNotFound("app_data".to_string())),
                },
            ),
            AppDataSelectMode::ManyJson(_, _, _) => AppDataQueryResult::Many(
                match query_rows!(&conn, &query_str, params![&query.query.to_string()], |x| {
                    Self::get_app_data_from_row(x)
                }) {
                    Ok(x) => x,
                    Err(_) => return Err(Error::GeneralNotFound("app_data".to_string())),
                },
            ),
        };

        Ok(res)
    }

    /// Delete all app_data matched by the given query.
    pub async fn query_delete_app_data(&self, query: AppDataQuery) -> Result<()> {
        let conn = match self.0.connect().await {
            Ok(c) => c,
            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
        };

        let query_str = query
            .to_string()
            .replace("%q%", &query.query.selector())
            .replace("SELECT * FROM", "SELECT id FROM");

        if let Err(e) = execute!(
            &conn,
            &format!("DELETE FROM app_data WHERE id IN ({query_str})"),
            params![&query.query.to_string()]
        ) {
            return Err(Error::MiscError(e.to_string()));
        }

        Ok(())
    }

    const MAXIMUM_FREE_APP_DATA: usize = 5;
    const MAXIMUM_DATA_SIZE: usize = 205_000;

    /// Create a new app_data in the database.
    ///
    /// # Arguments
    /// * `data` - a mock [`AppData`] object to insert
    pub async fn create_app_data(&self, data: AppData) -> Result<AppData> {
        let app = self.get_app_by_id(data.app).await?;

        // check values
        if data.key.is_empty() {
            return Err(Error::DataTooShort("key".to_string()));
        } else if data.key.len() > 128 {
            return Err(Error::DataTooLong("key".to_string()));
        }

        if data.value.is_empty() {
            return Err(Error::DataTooShort("value".to_string()));
        } else if data.value.len() > Self::MAXIMUM_DATA_SIZE {
            return Err(Error::DataTooLong("value".to_string()));
        }

        // check number of app_data
        let owner = self.get_user_by_id(app.owner).await?;

        if !owner.permissions.check(FinePermission::SUPPORTER) {
            let app_data = self
                .get_table_row_count_where("app_data", &format!("app = {}", data.app))
                .await? as usize;

            if app_data >= Self::MAXIMUM_FREE_APP_DATA {
                return Err(Error::MiscError(
                    "You already have the maximum number of app_data you can have".to_string(),
                ));
            }
        }

        // ...
        let conn = match self.0.connect().await {
            Ok(c) => c,
            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
        };

        let res = execute!(
            &conn,
            "INSERT INTO app_data VALUES ($1, $2, $3, $4)",
            params![
                &(data.id as i64),
                &(data.app as i64),
                &data.key,
                &data.value
            ]
        );

        if let Err(e) = res {
            return Err(Error::DatabaseError(e.to_string()));
        }

        Ok(data)
    }

    pub async fn delete_app_data(&self, id: usize) -> Result<()> {
        let conn = match self.0.connect().await {
            Ok(c) => c,
            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
        };

        let res = execute!(&conn, "DELETE FROM app_data WHERE id = $1", &[&(id as i64)]);

        if let Err(e) = res {
            return Err(Error::DatabaseError(e.to_string()));
        }

        self.0.1.remove(format!("atto.app_data:{}", id)).await;
        Ok(())
    }

    auto_method!(update_app_data_key(&str) -> "UPDATE app_data SET k = $1 WHERE id = $2" --cache-key-tmpl="atto.app_data:{}");
    auto_method!(update_app_data_value(&str) -> "UPDATE app_data SET v = $1 WHERE id = $2" --cache-key-tmpl="atto.app_data:{}");
}