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