tetratto_core/database/
apps.rs1use oiseau::cache::Cache;
2use crate::model::{
3 apps::{AppQuota, ThirdPartyApp, DeveloperPassStorageQuota},
4 auth::User,
5 oauth::AppScope,
6 permissions::{FinePermission, SecondaryPermission},
7 Error, Result,
8};
9use crate::{auto_method, DataManager};
10use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
11
12impl DataManager {
13 pub(crate) fn get_app_from_row(x: &PostgresRow) -> ThirdPartyApp {
15 ThirdPartyApp {
16 id: get!(x->0(i64)) as usize,
17 created: get!(x->1(i64)) as usize,
18 owner: get!(x->2(i64)) as usize,
19 title: get!(x->3(String)),
20 homepage: get!(x->4(String)),
21 redirect: get!(x->5(String)),
22 quota_status: serde_json::from_str(&get!(x->6(String))).unwrap(),
23 banned: get!(x->7(i32)) as i8 == 1,
24 grants: get!(x->8(i32)) as usize,
25 scopes: serde_json::from_str(&get!(x->9(String))).unwrap(),
26 api_key: get!(x->10(String)),
27 data_used: get!(x->11(i32)) as usize,
28 storage_capacity: serde_json::from_str(&get!(x->12(String))).unwrap(),
29 }
30 }
31
32 auto_method!(get_app_by_id(usize as i64)@get_app_from_row -> "SELECT * FROM apps WHERE id = $1" --name="app" --returns=ThirdPartyApp --cache-key-tmpl="atto.app:{}");
33 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:{}");
34
35 pub async fn get_apps_by_owner(&self, id: usize) -> Result<Vec<ThirdPartyApp>> {
40 let conn = match self.0.connect().await {
41 Ok(c) => c,
42 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
43 };
44
45 let res = query_rows!(
46 &conn,
47 "SELECT * FROM apps WHERE owner = $1 ORDER BY created DESC",
48 &[&(id as i64)],
49 |x| { Self::get_app_from_row(x) }
50 );
51
52 if res.is_err() {
53 return Err(Error::GeneralNotFound("app".to_string()));
54 }
55
56 Ok(res.unwrap())
57 }
58
59 const MAXIMUM_FREE_APPS: usize = 1;
60
61 pub async fn create_app(&self, data: ThirdPartyApp) -> Result<ThirdPartyApp> {
66 if data.title.trim().len() < 2 {
68 return Err(Error::DataTooShort("title".to_string()));
69 } else if data.title.len() > 32 {
70 return Err(Error::DataTooLong("title".to_string()));
71 }
72
73 let owner = self.get_user_by_id(data.owner).await?;
75
76 if !owner
77 .secondary_permissions
78 .check(SecondaryPermission::DEVELOPER_PASS)
79 {
80 let apps = self
81 .get_table_row_count_where("apps", &format!("owner = {}", owner.id))
82 .await? as usize;
83
84 if apps >= Self::MAXIMUM_FREE_APPS {
85 return Err(Error::MiscError(
86 "You already have the maximum number of apps you can have".to_string(),
87 ));
88 }
89 }
90
91 let conn = match self.0.connect().await {
93 Ok(c) => c,
94 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
95 };
96
97 let res = execute!(
98 &conn,
99 "INSERT INTO apps VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
100 params![
101 &(data.id as i64),
102 &(data.created as i64),
103 &(data.owner as i64),
104 &data.title,
105 &data.homepage,
106 &data.redirect,
107 &serde_json::to_string(&data.quota_status).unwrap(),
108 &{ if data.banned { 1 } else { 0 } },
109 &(data.grants as i32),
110 &serde_json::to_string(&data.scopes).unwrap(),
111 &data.api_key,
112 &(data.data_used as i32),
113 &serde_json::to_string(&data.storage_capacity).unwrap(),
114 ]
115 );
116
117 if let Err(e) = res {
118 return Err(Error::DatabaseError(e.to_string()));
119 }
120
121 Ok(data)
122 }
123
124 pub async fn delete_app(&self, id: usize, user: &User) -> Result<()> {
125 let app = self.get_app_by_id(id).await?;
126
127 if user.id != app.owner && !user.permissions.check(FinePermission::MANAGE_APPS) {
129 return Err(Error::NotAllowed);
130 }
131
132 let conn = match self.0.connect().await {
134 Ok(c) => c,
135 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
136 };
137
138 let res = execute!(&conn, "DELETE FROM apps WHERE id = $1", &[&(id as i64)]);
139
140 if let Err(e) = res {
141 return Err(Error::DatabaseError(e.to_string()));
142 }
143
144 self.cache_clear_app(&app).await;
145
146 let res = execute!(
148 &conn,
149 "DELETE FROM app_data WHERE app = $1",
150 &[&(id as i64)]
151 );
152
153 if let Err(e) = res {
154 return Err(Error::DatabaseError(e.to_string()));
155 }
156
157 Ok(())
159 }
160
161 pub async fn cache_clear_app(&self, app: &ThirdPartyApp) {
162 self.0.1.remove(format!("atto.app:{}", app.id)).await;
163 self.0.1.remove(format!("atto.app_k:{}", app.api_key)).await;
164 }
165
166 auto_method!(update_app_title(&str)@get_app_by_id:FinePermission::MANAGE_APPS; -> "UPDATE apps SET title = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
167 auto_method!(update_app_homepage(&str)@get_app_by_id:FinePermission::MANAGE_APPS; -> "UPDATE apps SET homepage = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
168 auto_method!(update_app_redirect(&str)@get_app_by_id:FinePermission::MANAGE_APPS; -> "UPDATE apps SET redirect = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
169 auto_method!(update_app_quota_status(AppQuota)@get_app_by_id -> "UPDATE apps SET quota_status = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_app);
170 auto_method!(update_app_scopes(Vec<AppScope>)@get_app_by_id:FinePermission::MANAGE_APPS; -> "UPDATE apps SET scopes = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_app);
171 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);
172 auto_method!(update_app_storage_capacity(DeveloperPassStorageQuota)@get_app_by_id -> "UPDATE apps SET storage_capacity = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_app);
173
174 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);
175 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);
176
177 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);
178 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);
179}