1use std::collections::HashMap;
2
3use crate::model::auth::Notification;
4use crate::model::{auth::User, mail::Letter, permissions::SecondaryPermission, Error, Result};
5use crate::{auto_method, DataManager};
6use oiseau::{cache::Cache, execute, get, params, query_rows, PostgresRow};
7
8impl DataManager {
9 pub(crate) fn get_letter_from_row(x: &PostgresRow) -> Letter {
11 Letter {
12 id: get!(x->0(i64)) as usize,
13 created: get!(x->1(i64)) as usize,
14 owner: get!(x->2(i64)) as usize,
15 receivers: serde_json::from_str(&get!(x->3(String))).unwrap(),
16 subject: get!(x->4(String)),
17 content: get!(x->5(String)),
18 read_by: serde_json::from_str(&get!(x->6(String))).unwrap(),
19 replying_to: get!(x->7(i64)) as usize,
20 likes: get!(x->8(i32)) as isize,
21 dislikes: get!(x->9(i32)) as isize,
22 }
23 }
24
25 auto_method!(get_letter_by_id(usize as i64)@get_letter_from_row -> "SELECT * FROM letters WHERE id = $1" --name="letter" --returns=Letter --cache-key-tmpl="atto.letter:{}");
26
27 pub async fn get_letters_by_user(
34 &self,
35 id: usize,
36 batch: usize,
37 page: usize,
38 ) -> Result<Vec<Letter>> {
39 let conn = match self.0.connect().await {
40 Ok(c) => c,
41 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
42 };
43
44 let res = query_rows!(
45 &conn,
46 "SELECT * FROM letters WHERE owner = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
47 &[&(id as i64), &(batch as i64), &((page * batch) as i64)],
48 |x| { Self::get_letter_from_row(x) }
49 );
50
51 if res.is_err() {
52 return Err(Error::GeneralNotFound("letter".to_string()));
53 }
54
55 Ok(res.unwrap())
56 }
57
58 pub async fn get_received_letters_by_user(
65 &self,
66 id: usize,
67 batch: usize,
68 page: usize,
69 ) -> Result<Vec<Letter>> {
70 let conn = match self.0.connect().await {
71 Ok(c) => c,
72 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
73 };
74
75 let res = query_rows!(
76 &conn,
77 "SELECT * FROM letters WHERE receivers LIKE $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
78 &[
79 &format!("%{id}%"),
80 &(batch as i64),
81 &((page * batch) as i64)
82 ],
83 |x| { Self::get_letter_from_row(x) }
84 );
85
86 if res.is_err() {
87 return Err(Error::GeneralNotFound("letter".to_string()));
88 }
89
90 Ok(res.unwrap())
91 }
92
93 pub async fn get_letters_by_replying_to(
100 &self,
101 id: usize,
102 batch: usize,
103 page: usize,
104 ) -> Result<Vec<Letter>> {
105 let conn = match self.0.connect().await {
106 Ok(c) => c,
107 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
108 };
109
110 let res = query_rows!(
111 &conn,
112 "SELECT * FROM letters WHERE replying_to = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
113 &[&(id as i64), &(batch as i64), &((page * batch) as i64)],
114 |x| { Self::get_letter_from_row(x) }
115 );
116
117 if res.is_err() {
118 return Err(Error::GeneralNotFound("letter".to_string()));
119 }
120
121 Ok(res.unwrap())
122 }
123
124 pub async fn fill_letters(&self, letters: Vec<Letter>) -> Result<Vec<(User, Letter)>> {
126 let mut seen_users: HashMap<usize, User> = HashMap::new();
127 let mut out = Vec::new();
128
129 for letter in letters {
130 out.push(if let Some(ua) = seen_users.get(&letter.owner) {
131 (ua.to_owned(), letter)
132 } else {
133 let user = self.get_user_by_id(letter.owner).await?;
134 seen_users.insert(letter.owner, user.clone());
135 (user, letter)
136 })
137 }
138
139 Ok(out)
140 }
141
142 pub async fn create_letter(&self, data: Letter) -> Result<Letter> {
147 if data.subject.len() < 2 {
149 return Err(Error::DataTooShort("subject".to_string()));
150 } else if data.subject.len() > 256 {
151 return Err(Error::DataTooLong("subject".to_string()));
152 }
153
154 if data.content.len() < 2 {
155 return Err(Error::DataTooShort("content".to_string()));
156 } else if data.content.len() > 16384 {
157 return Err(Error::DataTooLong("content".to_string()));
158 }
159
160 if data.receivers.len() < 1 {
161 return Err(Error::DataTooShort("receivers".to_string()));
162 } else if data.receivers.len() > 10 {
163 return Err(Error::DataTooLong("receivers".to_string()));
164 }
165
166 let sender = self.get_user_by_id(data.owner).await?;
168
169 let conn = match self.0.connect().await {
171 Ok(c) => c,
172 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
173 };
174
175 let res = execute!(
176 &conn,
177 "INSERT INTO letters VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
178 params![
179 &(data.id as i64),
180 &(data.created as i64),
181 &(data.owner as i64),
182 &serde_json::to_string(&data.receivers).unwrap(),
183 &data.subject,
184 &data.content,
185 &serde_json::to_string(&data.read_by).unwrap(),
186 &(data.replying_to as i64),
187 &(data.likes as i32),
188 &(data.dislikes as i32),
189 ]
190 );
191
192 if let Err(e) = res {
193 return Err(Error::DatabaseError(e.to_string()));
194 }
195
196 for x in &data.receivers {
198 self.create_notification(Notification::new(
199 "You've got mail!".to_string(),
200 format!(
201 "[@{}](/api/v1/auth/user/find/{}) has sent you a [letter](/mail/letter/{}).",
202 sender.username, sender.id, data.id
203 ),
204 *x,
205 ))
206 .await?;
207 }
208
209 Ok(data)
211 }
212
213 pub async fn delete_letter(&self, id: usize, user: &User) -> Result<()> {
214 let letter = self.get_letter_by_id(id).await?;
215
216 if user.id != letter.owner
218 && !user
219 .secondary_permissions
220 .check(SecondaryPermission::MANAGE_LETTERS)
221 {
222 return Err(Error::NotAllowed);
223 }
224
225 let conn = match self.0.connect().await {
227 Ok(c) => c,
228 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
229 };
230
231 let res = execute!(&conn, "DELETE FROM letters WHERE id = $1", &[&(id as i64)]);
232
233 if let Err(e) = res {
234 return Err(Error::DatabaseError(e.to_string()));
235 }
236
237 self.0.1.remove(format!("atto.letter:{}", id)).await;
239 Ok(())
240 }
241
242 auto_method!(update_letter_read_by(Vec<usize>) -> "UPDATE letters SET read_by = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.letter:{}");
243
244 auto_method!(incr_letter_likes() -> "UPDATE letters SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.letter:{}" --incr);
245 auto_method!(incr_letter_dislikes() -> "UPDATE letters SET dislikes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.letter:{}" --incr);
246 auto_method!(decr_letter_likes()@get_letter_by_id -> "UPDATE letters SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.letter:{}" --decr=likes);
247 auto_method!(decr_letter_dislikes()@get_letter_by_id -> "UPDATE letters SET dislikes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.letter:{}" --decr=dislikes);
248}