tetratto_core/database/
invite_codes.rs1use oiseau::{cache::Cache, query_row, query_rows};
2use tetratto_shared::unix_epoch_timestamp;
3use crate::model::{
4 Error, Result,
5 auth::{User, InviteCode},
6 permissions::FinePermission,
7};
8use crate::{auto_method, DataManager};
9use oiseau::{PostgresRow, execute, get, params};
10
11impl DataManager {
12 pub(crate) fn get_invite_code_from_row(x: &PostgresRow) -> InviteCode {
14 InviteCode {
15 id: get!(x->0(i64)) as usize,
16 created: get!(x->1(i64)) as usize,
17 owner: get!(x->2(i64)) as usize,
18 code: get!(x->3(String)),
19 is_used: get!(x->4(i32)) as i8 == 1,
20 }
21 }
22
23 auto_method!(get_invite_code_by_id()@get_invite_code_from_row -> "SELECT * FROM invite_codes WHERE id = $1" --name="invite code" --returns=InviteCode --cache-key-tmpl="atto.invite_code:{}");
24 auto_method!(get_invite_code_by_code(&str)@get_invite_code_from_row -> "SELECT * FROM invite_codes WHERE code = $1" --name="invite code" --returns=InviteCode);
25
26 pub async fn get_invite_codes_by_owner(
28 &self,
29 owner: usize,
30 batch: usize,
31 page: usize,
32 ) -> Result<Vec<InviteCode>> {
33 let conn = match self.0.connect().await {
34 Ok(c) => c,
35 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
36 };
37
38 let res = query_rows!(
39 &conn,
40 "SELECT * FROM invite_codes WHERE owner = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
41 &[&(owner as i64), &(batch as i64), &((page * batch) as i64)],
42 |x| { Self::get_invite_code_from_row(x) }
43 );
44
45 if res.is_err() {
46 return Err(Error::GeneralNotFound("invite_code".to_string()));
47 }
48
49 Ok(res.unwrap())
50 }
51
52 pub async fn get_invite_codes_by_owner_count(&self, owner: usize) -> Result<i32> {
54 let conn = match self.0.connect().await {
55 Ok(c) => c,
56 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
57 };
58
59 let res = query_row!(
60 &conn,
61 "SELECT COUNT(*)::int FROM invite_codes WHERE owner = $1",
62 &[&(owner as i64)],
63 |x| Ok(x.get::<usize, i32>(0))
64 );
65
66 if res.is_err() {
67 return Err(Error::GeneralNotFound("invite_code".to_string()));
68 }
69
70 Ok(res.unwrap())
71 }
72
73 pub async fn fill_invite_codes(
75 &self,
76 codes: Vec<InviteCode>,
77 ) -> Result<Vec<(Option<User>, InviteCode)>> {
78 let mut out = Vec::new();
79
80 for code in codes {
81 if code.is_used {
82 out.push((
83 (self.get_user_by_invite_code(code.id as i64).await).ok(),
84 code,
85 ))
86 } else {
87 out.push((None, code))
88 }
89 }
90
91 Ok(out)
92 }
93
94 const MAXIMUM_FREE_INVITE_CODES: usize = 4;
95 const MAXIMUM_SUPPORTER_INVITE_CODES: usize = 48;
96 const MINIMUM_ACCOUNT_AGE_FOR_INVITE_CODES: usize = 2_629_800_000; pub async fn create_invite_code(&self, data: InviteCode, user: &User) -> Result<InviteCode> {
103 if !user.permissions.check(FinePermission::SUPPORTER) | user.was_purchased
105 && unix_epoch_timestamp() - user.created < Self::MINIMUM_ACCOUNT_AGE_FOR_INVITE_CODES {
106 return Err(Error::MiscError(
107 "Your account is too young to do this".to_string(),
108 ));
109 }
110
111 if !user.permissions.check(FinePermission::SUPPORTER) {
113 if (self.get_invite_codes_by_owner_count(user.id).await? as usize)
116 >= Self::MAXIMUM_FREE_INVITE_CODES
117 {
118 return Err(Error::MiscError(
119 "You already have the maximum number of invite codes you can create"
120 .to_string(),
121 ));
122 }
123 } else if !user.permissions.check(FinePermission::MANAGE_USERS) {
124 if (self.get_invite_codes_by_owner_count(user.id).await? as usize)
126 >= Self::MAXIMUM_SUPPORTER_INVITE_CODES
127 {
128 return Err(Error::MiscError(
129 "You already have the maximum number of invite codes you can create"
130 .to_string(),
131 ));
132 }
133 }
134
135 let conn = match self.0.connect().await {
136 Ok(c) => c,
137 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
138 };
139
140 let res = execute!(
141 &conn,
142 "INSERT INTO invite_codes VALUES ($1, $2, $3, $4, $5)",
143 params![
144 &(data.id as i64),
145 &(data.created as i64),
146 &(data.owner as i64),
147 &data.code,
148 &{ if data.is_used { 1 } else { 0 } }
149 ]
150 );
151
152 if let Err(e) = res {
153 return Err(Error::DatabaseError(e.to_string()));
154 }
155
156 Ok(data)
157 }
158
159 pub async fn delete_invite_code(&self, id: usize, user: &User) -> Result<()> {
160 if !user.permissions.check(FinePermission::MANAGE_USERS) {
161 return Err(Error::NotAllowed);
162 }
163
164 let conn = match self.0.connect().await {
165 Ok(c) => c,
166 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
167 };
168
169 let res = execute!(
170 &conn,
171 "DELETE FROM invite_codes WHERE id = $1",
172 &[&(id as i64)]
173 );
174
175 if let Err(e) = res {
176 return Err(Error::DatabaseError(e.to_string()));
177 }
178
179 self.0.1.remove(format!("atto.invite_code:{}", id)).await;
180
181 Ok(())
182 }
183
184 pub async fn update_invite_code_is_used(&self, id: usize, new_is_used: bool) -> Result<()> {
185 let conn = match self.0.connect().await {
186 Ok(c) => c,
187 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
188 };
189
190 let res = execute!(
191 &conn,
192 "UPDATE invite_codes SET is_used = $1 WHERE id = $2",
193 params![&{ if new_is_used { 1 } else { 0 } }, &(id as i64)]
194 );
195
196 if let Err(e) = res {
197 return Err(Error::DatabaseError(e.to_string()));
198 }
199
200 self.0.1.remove(format!("atto.invite_code:{}", id)).await;
201 Ok(())
202 }
203}