1use oiseau::cache::Cache;
2use crate::{
3 database::posts::FullPost,
4 model::{
5 auth::User,
6 permissions::FinePermission,
7 stacks::{StackMode, StackPrivacy, StackSort, UserStack},
8 Error, Result,
9 },
10};
11use crate::{auto_method, DataManager};
12use oiseau::{PostgresRow, execute, get, query_rows, params};
13
14impl DataManager {
15 pub(crate) fn get_stack_from_row(x: &PostgresRow) -> UserStack {
17 UserStack {
18 id: get!(x->0(i64)) as usize,
19 created: get!(x->1(i64)) as usize,
20 owner: get!(x->2(i64)) as usize,
21 name: get!(x->3(String)),
22 users: serde_json::from_str(&get!(x->4(String))).unwrap(),
23 privacy: serde_json::from_str(&get!(x->5(String))).unwrap(),
24 mode: serde_json::from_str(&get!(x->6(String))).unwrap(),
25 sort: serde_json::from_str(&get!(x->7(String))).unwrap(),
26 is_locked: get!(x->8(i32)) == 1,
27 }
28 }
29
30 auto_method!(get_stack_by_id(usize as i64)@get_stack_from_row -> "SELECT * FROM stacks WHERE id = $1" --name="stack" --returns=UserStack --cache-key-tmpl="atto.stack:{}");
31
32 pub async fn get_stack_posts(
33 &self,
34 as_user_id: usize,
35 id: usize,
36 batch: usize,
37 page: usize,
38 ignore_users: &Vec<usize>,
39 user: &Option<User>,
40 ) -> Result<Vec<FullPost>> {
41 let stack = self.get_stack_by_id(id).await?;
42
43 Ok(match stack.mode {
44 StackMode::Include => {
45 self.fill_posts_with_community(
46 self.get_posts_from_stack(id, batch, page, stack.sort)
47 .await?,
48 as_user_id,
49 ignore_users,
50 user,
51 )
52 .await?
53 .0
54 }
55 StackMode::Exclude => {
56 let ignore_users = [ignore_users.to_owned(), stack.users].concat();
57
58 match stack.sort {
59 StackSort::Created => {
60 self.fill_posts_with_community(
61 self.get_latest_posts(batch, user, 0).await?,
62 as_user_id,
63 &ignore_users,
64 user,
65 )
66 .await?
67 .0
68 }
69 StackSort::Likes => {
70 self.fill_posts_with_community(
71 self.get_popular_posts(batch, page, 604_800_000).await?,
72 as_user_id,
73 &ignore_users,
74 user,
75 )
76 .await?
77 .0
78 }
79 }
80 }
81 StackMode::BlockList => {
82 return Err(Error::MiscError(
83 "You should use `get_stack_users` for this type".to_string(),
84 ));
85 }
86 StackMode::Circle => {
87 if !stack.users.contains(&as_user_id) && as_user_id != stack.owner {
88 return Err(Error::NotAllowed);
89 }
90
91 self.fill_posts_with_community(
92 self.get_posts_by_stack(stack.id, batch, page).await?,
93 as_user_id,
94 ignore_users,
95 user,
96 )
97 .await?
98 .0
99 }
100 })
101 }
102
103 pub async fn get_stack_users(&self, id: usize, batch: usize, page: usize) -> Result<Vec<User>> {
104 let stack = self.get_stack_by_id(id).await?;
105
106 if stack.mode != StackMode::BlockList {
107 return Err(Error::MiscError(
108 "You should use `get_stack_posts` for this type".to_string(),
109 ));
110 }
111
112 let mut out = Vec::new();
114
115 for (i, user) in stack.users.iter().skip(batch * page).enumerate() {
116 if i == batch {
117 break;
118 }
119
120 out.push(self.get_user_by_id(user.to_owned()).await?);
121 }
122
123 Ok(out)
124 }
125
126 pub async fn get_stacks_by_user(&self, id: usize) -> Result<Vec<UserStack>> {
133 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 = query_rows!(
139 &conn,
140 "SELECT * FROM stacks WHERE owner = $1 OR (mode = '\"Circle\"' AND users LIKE $2) ORDER BY name ASC",
141 &[&(id as i64), &format!("%{id}%")],
142 |x| { Self::get_stack_from_row(x) }
143 );
144
145 if res.is_err() {
146 return Err(Error::GeneralNotFound("stack".to_string()));
147 }
148
149 Ok(res.unwrap())
150 }
151
152 const MAXIMUM_FREE_STACKS: usize = 5;
153 pub const MAXIMUM_FREE_STACK_USERS: usize = 50;
154
155 pub async fn create_stack(&self, data: UserStack) -> Result<UserStack> {
160 if data.name.trim().len() < 2 {
162 return Err(Error::DataTooShort("title".to_string()));
163 } else if data.name.len() > 32 {
164 return Err(Error::DataTooLong("title".to_string()));
165 }
166
167 let owner = self.get_user_by_id(data.owner).await?;
169
170 if !owner.permissions.check(FinePermission::SUPPORTER) {
171 let stacks = self
172 .get_table_row_count_where("stacks", &format!("owner = {}", owner.id))
173 .await? as usize;
174
175 if stacks >= Self::MAXIMUM_FREE_STACKS {
176 return Err(Error::MiscError(
177 "You already have the maximum number of stacks you can have".to_string(),
178 ));
179 }
180 }
181
182 let conn = match self.0.connect().await {
184 Ok(c) => c,
185 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
186 };
187
188 let res = execute!(
189 &conn,
190 "INSERT INTO stacks VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
191 params![
192 &(data.id as i64),
193 &(data.created as i64),
194 &(data.owner as i64),
195 &data.name,
196 &serde_json::to_string(&data.users).unwrap(),
197 &serde_json::to_string(&data.privacy).unwrap(),
198 &serde_json::to_string(&data.mode).unwrap(),
199 &serde_json::to_string(&data.sort).unwrap(),
200 &if data.is_locked { 1 } else { 0 },
201 ]
202 );
203
204 if let Err(e) = res {
205 return Err(Error::DatabaseError(e.to_string()));
206 }
207
208 Ok(data)
209 }
210
211 pub async fn delete_stack(&self, id: usize, user: &User) -> Result<()> {
212 let stack = self.get_stack_by_id(id).await?;
213
214 if stack.is_locked {
215 return Err(Error::NotAllowed);
216 }
217
218 if user.id != stack.owner && !user.permissions.check(FinePermission::MANAGE_STACKS) {
220 return Err(Error::NotAllowed);
221 }
222
223 let conn = match self.0.connect().await {
225 Ok(c) => c,
226 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
227 };
228
229 let res = execute!(&conn, "DELETE FROM stacks WHERE id = $1", &[&(id as i64)]);
230
231 if let Err(e) = res {
232 return Err(Error::DatabaseError(e.to_string()));
233 }
234
235 let res = execute!(
237 &conn,
238 "DELETE FROM stackblocks WHERE stack = $1",
239 &[&(id as i64)]
240 );
241
242 if let Err(e) = res {
243 return Err(Error::DatabaseError(e.to_string()));
244 }
245
246 let res = execute!(&conn, "DELETE FROM posts WHERE stack = $1", &[&(id as i64)]);
248
249 if let Err(e) = res {
250 return Err(Error::DatabaseError(e.to_string()));
251 }
252
253 self.0.1.remove(format!("atto.stack:{}", id)).await;
255 Ok(())
256 }
257
258 pub async fn clone_stack(&self, owner: usize, stack: usize) -> Result<UserStack> {
260 let stack = self.get_stack_by_id(stack).await?;
261 self.create_stack(UserStack::new(stack.name, owner, stack.users))
262 .await
263 }
264
265 auto_method!(update_stack_name(&str)@get_stack_by_id:FinePermission::MANAGE_STACKS; -> "UPDATE stacks SET name = $1 WHERE id = $2" --cache-key-tmpl="atto.stack:{}");
266 auto_method!(update_stack_users(Vec<usize>)@get_stack_by_id:FinePermission::MANAGE_STACKS; -> "UPDATE stacks SET users = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.stack:{}");
267
268 auto_method!(update_stack_privacy(StackPrivacy)@get_stack_by_id:FinePermission::MANAGE_STACKS; -> "UPDATE stacks SET privacy = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.stack:{}");
269 auto_method!(update_stack_mode(StackMode)@get_stack_by_id:FinePermission::MANAGE_STACKS; -> "UPDATE stacks SET mode = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.stack:{}");
270 auto_method!(update_stack_sort(StackSort)@get_stack_by_id:FinePermission::MANAGE_STACKS; -> "UPDATE stacks SET sort = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.stack:{}");
271}