Skip to main content

tetratto_core2/database/
apps.rs

1use oiseau::cache::Cache;
2use crate::model::{
3    apps::{ThirdPartyApp},
4    auth::User,
5    oauth::AppScope,
6    permissions::FinePermission,
7    Error, Result,
8};
9use crate::{auto_method, DataManager};
10use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
11
12impl DataManager {
13    /// Get a [`ThirdPartyApp`] from an SQL row.
14    pub(crate) fn get_app_from_row(x: &PostgresRow) -> ThirdPartyApp {
15        ThirdPartyApp {
16            id: crate::model::id::Id::deserialize(&get!(x->0(String))),
17            created: get!(x->1(i64)) as u128,
18            owner: crate::model::id::Id::deserialize(&get!(x->2(String))),
19            title: get!(x->3(String)),
20            homepage: get!(x->4(String)),
21            redirect: get!(x->5(String)),
22            banned: get!(x->6(i32)) as i8 == 1,
23            grants: get!(x->7(i32)) as usize,
24            scopes: serde_json::from_str(&get!(x->8(String))).unwrap(),
25            api_key: get!(x->9(String)),
26        }
27    }
28
29    auto_method!(get_app_by_id()@get_app_from_row -> "SELECT * FROM apps WHERE id = $1" --name="app" --returns=ThirdPartyApp --cache-key-tmpl="atto.app:{}");
30    auto_method!(get_app_by_api_key(&str)@get_app_from_row -> "SELECT * FROM apps WHERE api_key = $1" --name="app" --returns=ThirdPartyApp --cache-key-tmpl="atto.app_k:{}");
31
32    /// Get all apps by user.
33    ///
34    /// # Arguments
35    /// * `id` - the ID of the user to fetch apps for
36    pub async fn get_apps_by_owner(&self, id: &crate::model::id::Id) -> Result<Vec<ThirdPartyApp>> {
37        let conn = match self.0.connect().await {
38            Ok(c) => c,
39            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
40        };
41
42        let res = query_rows!(
43            &conn,
44            "SELECT * FROM apps WHERE owner = $1 ORDER BY created DESC",
45            &[&id.printable()],
46            |x| { Self::get_app_from_row(x) }
47        );
48
49        if res.is_err() {
50            return Err(Error::GeneralNotFound("app".to_string()));
51        }
52
53        Ok(res.unwrap())
54    }
55
56    const MAXIMUM_FREE_APPS: usize = 1;
57
58    /// Create a new app in the database.
59    ///
60    /// # Arguments
61    /// * `data` - a mock [`ThirdPartyApp`] object to insert
62    pub async fn create_app(&self, data: ThirdPartyApp) -> Result<ThirdPartyApp> {
63        // check values
64        if data.title.trim().len() < 2 {
65            return Err(Error::DataTooShort("title".to_string()));
66        } else if data.title.len() > 32 {
67            return Err(Error::DataTooLong("title".to_string()));
68        }
69
70        // check number of apps
71        let owner = self.get_user_by_id(&data.owner).await?;
72
73        if !owner.permissions.check(FinePermission::Supporter) {
74            let apps = self
75                .get_table_row_count_where("apps", &format!("owner = {}", owner.id))
76                .await? as usize;
77
78            if apps >= Self::MAXIMUM_FREE_APPS {
79                return Err(Error::MiscError(
80                    "You already have the maximum number of apps you can have".to_string(),
81                ));
82            }
83        }
84
85        // ...
86        let conn = match self.0.connect().await {
87            Ok(c) => c,
88            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
89        };
90
91        let res = execute!(
92            &conn,
93            "INSERT INTO apps VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
94            params![
95                &data.id.printable(),
96                &(data.created as i64),
97                &data.owner.printable(),
98                &data.title,
99                &data.homepage,
100                &data.redirect,
101                &{ if data.banned { 1 } else { 0 } },
102                &(data.grants as i32),
103                &serde_json::to_string(&data.scopes).unwrap(),
104                &data.api_key,
105            ]
106        );
107
108        if let Err(e) = res {
109            return Err(Error::DatabaseError(e.to_string()));
110        }
111
112        Ok(data)
113    }
114
115    pub async fn delete_app(&self, id: &crate::model::id::Id, user: &User) -> Result<()> {
116        let app = self.get_app_by_id(id).await?;
117
118        // check user permission
119        if user.id != app.owner && !user.permissions.check(FinePermission::ManageApps) {
120            return Err(Error::NotAllowed);
121        }
122
123        // ...
124        let conn = match self.0.connect().await {
125            Ok(c) => c,
126            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
127        };
128
129        let res = execute!(&conn, "DELETE FROM apps WHERE id = $1", &[&id.printable()]);
130
131        if let Err(e) = res {
132            return Err(Error::DatabaseError(e.to_string()));
133        }
134
135        self.cache_clear_app(&app).await;
136
137        // remove data
138        let res = execute!(
139            &conn,
140            "DELETE FROM app_data WHERE app = $1",
141            &[&id.printable()]
142        );
143
144        if let Err(e) = res {
145            return Err(Error::DatabaseError(e.to_string()));
146        }
147
148        // ...
149        Ok(())
150    }
151
152    pub async fn cache_clear_app(&self, app: &ThirdPartyApp) {
153        self.0.1.remove(format!("atto.app:{}", app.id)).await;
154        self.0.1.remove(format!("atto.app_k:{}", app.api_key)).await;
155    }
156
157    auto_method!(update_app_title(&str)@get_app_by_id:FinePermission::ManageApps; -> "UPDATE apps SET title = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
158    auto_method!(update_app_homepage(&str)@get_app_by_id:FinePermission::ManageApps; -> "UPDATE apps SET homepage = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
159    auto_method!(update_app_redirect(&str)@get_app_by_id:FinePermission::ManageApps; -> "UPDATE apps SET redirect = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
160    auto_method!(update_app_scopes(Vec<AppScope>)@get_app_by_id:FinePermission::ManageApps; -> "UPDATE apps SET scopes = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_app);
161    auto_method!(update_app_api_key(&str)@get_app_by_id -> "UPDATE apps SET api_key = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
162
163    auto_method!(update_app_data_used(i32)@get_app_by_id -> "UPDATE apps SET data_used = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
164    auto_method!(add_app_data_used(i32)@get_app_by_id -> "UPDATE apps SET data_used = data_used + $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
165
166    auto_method!(incr_app_grants()@get_app_by_id -> "UPDATE apps SET grants = grants + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_app --incr);
167    auto_method!(decr_app_grants()@get_app_by_id -> "UPDATE apps SET grants = grants - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_app --decr=grants);
168}