tetratto_core/database/
app_data.rs

1use oiseau::cache::Cache;
2use crate::model::apps::{AppDataQuery, AppDataQueryResult, AppDataSelectMode};
3use crate::model::{apps::AppData, permissions::FinePermission, Error, Result};
4use crate::{auto_method, DataManager};
5use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
6
7pub const FREE_DATA_LIMIT: usize = 512_000;
8pub const PASS_DATA_LIMIT: usize = 26_214_400;
9
10impl DataManager {
11    /// Get a [`AppData`] from an SQL row.
12    pub(crate) fn get_app_data_from_row(x: &PostgresRow) -> AppData {
13        AppData {
14            id: get!(x->0(i64)) as usize,
15            app: get!(x->1(i64)) as usize,
16            key: get!(x->2(String)),
17            value: get!(x->3(String)),
18        }
19    }
20
21    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:{}");
22
23    /// Get all app_data by app.
24    ///
25    /// # Arguments
26    /// * `id` - the ID of the app to fetch app_data for
27    pub async fn get_app_data_by_app(&self, id: usize) -> Result<Vec<AppData>> {
28        let conn = match self.0.connect().await {
29            Ok(c) => c,
30            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
31        };
32
33        let res = query_rows!(
34            &conn,
35            "SELECT * FROM app_data WHERE app = $1 ORDER BY created DESC",
36            &[&(id as i64)],
37            |x| { Self::get_app_data_from_row(x) }
38        );
39
40        if res.is_err() {
41            return Err(Error::GeneralNotFound("app_data".to_string()));
42        }
43
44        Ok(res.unwrap())
45    }
46
47    /// Get all app_data by the given query.
48    pub async fn query_app_data(&self, query: AppDataQuery) -> Result<AppDataQueryResult> {
49        let conn = match self.0.connect().await {
50            Ok(c) => c,
51            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
52        };
53
54        let query_str = query.to_string().replace("%q%", &query.query.selector());
55
56        let res = match query.mode {
57            AppDataSelectMode::One(_) => AppDataQueryResult::One(
58                match query_row!(&conn, &query_str, params![&query.query.to_string()], |x| {
59                    Ok(Self::get_app_data_from_row(x))
60                }) {
61                    Ok(x) => x,
62                    Err(_) => return Err(Error::GeneralNotFound("app_data".to_string())),
63                },
64            ),
65            AppDataSelectMode::Many(_, _) => AppDataQueryResult::Many(
66                match query_rows!(&conn, &query_str, params![&query.query.to_string()], |x| {
67                    Self::get_app_data_from_row(x)
68                }) {
69                    Ok(x) => x,
70                    Err(_) => return Err(Error::GeneralNotFound("app_data".to_string())),
71                },
72            ),
73            AppDataSelectMode::ManyJson(_, _, _) => AppDataQueryResult::Many(
74                match query_rows!(&conn, &query_str, params![&query.query.to_string()], |x| {
75                    Self::get_app_data_from_row(x)
76                }) {
77                    Ok(x) => x,
78                    Err(_) => return Err(Error::GeneralNotFound("app_data".to_string())),
79                },
80            ),
81        };
82
83        Ok(res)
84    }
85
86    /// Delete all app_data matched by the given query.
87    pub async fn query_delete_app_data(&self, query: AppDataQuery) -> Result<()> {
88        let conn = match self.0.connect().await {
89            Ok(c) => c,
90            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
91        };
92
93        let query_str = query
94            .to_string()
95            .replace("%q%", &query.query.selector())
96            .replace("SELECT * FROM", "SELECT id FROM");
97
98        if let Err(e) = execute!(
99            &conn,
100            &format!("DELETE FROM app_data WHERE id IN ({query_str})"),
101            params![&query.query.to_string()]
102        ) {
103            return Err(Error::MiscError(e.to_string()));
104        }
105
106        Ok(())
107    }
108
109    const MAXIMUM_FREE_APP_DATA: usize = 5;
110    const MAXIMUM_DATA_SIZE: usize = 205_000;
111
112    /// Create a new app_data in the database.
113    ///
114    /// # Arguments
115    /// * `data` - a mock [`AppData`] object to insert
116    pub async fn create_app_data(&self, data: AppData) -> Result<AppData> {
117        let app = self.get_app_by_id(data.app).await?;
118
119        // check values
120        if data.key.len() < 1 {
121            return Err(Error::DataTooShort("key".to_string()));
122        } else if data.key.len() > 128 {
123            return Err(Error::DataTooLong("key".to_string()));
124        }
125
126        if data.value.len() < 1 {
127            return Err(Error::DataTooShort("value".to_string()));
128        } else if data.value.len() > Self::MAXIMUM_DATA_SIZE {
129            return Err(Error::DataTooLong("value".to_string()));
130        }
131
132        // check number of app_data
133        let owner = self.get_user_by_id(app.owner).await?;
134
135        if !owner.permissions.check(FinePermission::SUPPORTER) {
136            let app_data = self
137                .get_table_row_count_where("app_data", &format!("app = {}", data.app))
138                .await? as usize;
139
140            if app_data >= Self::MAXIMUM_FREE_APP_DATA {
141                return Err(Error::MiscError(
142                    "You already have the maximum number of app_data you can have".to_string(),
143                ));
144            }
145        }
146
147        // ...
148        let conn = match self.0.connect().await {
149            Ok(c) => c,
150            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
151        };
152
153        let res = execute!(
154            &conn,
155            "INSERT INTO app_data VALUES ($1, $2, $3, $4)",
156            params![
157                &(data.id as i64),
158                &(data.app as i64),
159                &data.key,
160                &data.value
161            ]
162        );
163
164        if let Err(e) = res {
165            return Err(Error::DatabaseError(e.to_string()));
166        }
167
168        Ok(data)
169    }
170
171    pub async fn delete_app_data(&self, id: usize) -> Result<()> {
172        let conn = match self.0.connect().await {
173            Ok(c) => c,
174            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
175        };
176
177        let res = execute!(&conn, "DELETE FROM app_data WHERE id = $1", &[&(id as i64)]);
178
179        if let Err(e) = res {
180            return Err(Error::DatabaseError(e.to_string()));
181        }
182
183        self.0.1.remove(format!("atto.app_data:{}", id)).await;
184        Ok(())
185    }
186
187    auto_method!(update_app_data_key(&str) -> "UPDATE app_data SET k = $1 WHERE id = $2" --cache-key-tmpl="atto.app_data:{}");
188    auto_method!(update_app_data_value(&str) -> "UPDATE app_data SET v = $1 WHERE id = $2" --cache-key-tmpl="atto.app_data:{}");
189}