add: close friends stack
This commit is contained in:
parent
407155e6c4
commit
5fafc8d7b9
83 changed files with 479 additions and 213 deletions
|
@ -130,6 +130,7 @@ impl DataManager {
|
|||
checkouts: serde_json::from_str(&get!(x->32(String)).to_string()).unwrap(),
|
||||
applied_configurations: serde_json::from_str(&get!(x->33(String)).to_string()).unwrap(),
|
||||
last_policy_consent: get!(x->34(i64)) as usize,
|
||||
close_friends_stack: get!(x->35(i64)) as usize,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,7 +287,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35)",
|
||||
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -322,7 +323,8 @@ impl DataManager {
|
|||
&(data.coins as i32),
|
||||
&serde_json::to_string(&data.checkouts).unwrap(),
|
||||
&serde_json::to_string(&data.applied_configurations).unwrap(),
|
||||
&(data.last_policy_consent as i64)
|
||||
&(data.last_policy_consent as i64),
|
||||
&(data.close_friends_stack as i64)
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -1138,6 +1140,7 @@ impl DataManager {
|
|||
auto_method!(update_user_checkouts(Vec<String>)@get_user_by_id -> "UPDATE users SET checkouts = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||
auto_method!(update_user_applied_configurations(Vec<(AppliedConfigType, usize)>)@get_user_by_id -> "UPDATE users SET applied_configurations = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||
auto_method!(update_user_last_policy_consent(i64)@get_user_by_id -> "UPDATE users SET last_policy_consent = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||
auto_method!(update_user_close_friends_stack(i64)@get_user_by_id -> "UPDATE users SET close_friends_stack = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||
|
||||
auto_method!(get_user_by_stripe_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE stripe_id = $1" --name="user" --returns=User);
|
||||
auto_method!(update_user_stripe_id(&str)@get_user_by_id -> "UPDATE users SET stripe_id = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||
|
|
|
@ -6,5 +6,6 @@ CREATE TABLE IF NOT EXISTS stacks (
|
|||
users TEXT NOT NULL,
|
||||
privacy TEXT NOT NULL,
|
||||
mode TEXT NOT NULL,
|
||||
sort TEXT NOT NULL
|
||||
sort TEXT NOT NULL,
|
||||
is_locked INT NOT NULL
|
||||
)
|
||||
|
|
|
@ -33,5 +33,6 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
coins INT NOT NULL,
|
||||
checkouts TEXT NOT NULL,
|
||||
applied_configurations TEXT NOT NULL,
|
||||
last_policy_consent BIGINT NOT NULL
|
||||
last_policy_consent BIGINT NOT NULL,
|
||||
close_friends_stack BIGINT NOT NULL
|
||||
)
|
||||
|
|
|
@ -77,3 +77,11 @@ ADD COLUMN IF NOT EXISTS likes INT DEFAULT 0;
|
|||
-- letters dislikes
|
||||
ALTER TABLE letters
|
||||
ADD COLUMN IF NOT EXISTS dislikes INT DEFAULT 0;
|
||||
|
||||
-- users close_friends_stack
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS close_friends_stack BIGINT DEFAULT 0;
|
||||
|
||||
-- stacks is_locked
|
||||
ALTER TABLE stacks
|
||||
ADD COLUMN IF NOT EXISTS is_locked INT DEFAULT 0;
|
||||
|
|
|
@ -1209,12 +1209,12 @@ impl DataManager {
|
|||
/// # Arguments
|
||||
/// * `id` - the ID of the stack the requested posts belong to
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
/// * `before` - the timestamp to pull posts before
|
||||
pub async fn get_posts_by_stack(
|
||||
&self,
|
||||
id: usize,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
before: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
|
@ -1223,8 +1223,17 @@ impl DataManager {
|
|||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM posts WHERE stack = $1 AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $2 OFFSET $3",
|
||||
&[&(id as i64), &(batch as i64), &((page * batch) as i64)],
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE stack = $1 AND replying_to = 0 AND is_deleted = 0{} ORDER BY created DESC LIMIT $2",
|
||||
{
|
||||
if before > 0 {
|
||||
format!(" AND created < {before}")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
),
|
||||
&[&(id as i64), &(batch as i64)],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
|
@ -1452,12 +1461,12 @@ impl DataManager {
|
|||
///
|
||||
/// # Arguments
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
/// * `before` - the timestamp to pull posts before
|
||||
/// * `cutoff` - the maximum number of milliseconds ago the post could have been created
|
||||
pub async fn get_popular_posts(
|
||||
&self,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
before: usize,
|
||||
cutoff: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
let conn = match self.0.connect().await {
|
||||
|
@ -1467,18 +1476,24 @@ impl DataManager {
|
|||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM posts WHERE replying_to = 0 AND NOT context LIKE '%\"is_nsfw\":true%' AND ($1 - created) < $2 ORDER BY likes - dislikes DESC, created ASC LIMIT $3 OFFSET $4",
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE replying_to = 0 AND NOT context LIKE '%\"is_nsfw\":true%' AND ($1 - created) < $2{} ORDER BY (likes - dislikes) DESC, created ASC LIMIT $3",
|
||||
if before > 0 {
|
||||
format!(" AND created < {before}")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
&[
|
||||
&(unix_epoch_timestamp() as i64),
|
||||
&(cutoff as i64),
|
||||
&(batch as i64),
|
||||
&((page * batch) as i64)
|
||||
],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1492,7 +1507,6 @@ impl DataManager {
|
|||
pub async fn get_latest_posts(
|
||||
&self,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
as_user: &Option<User>,
|
||||
before_time: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
|
@ -1518,7 +1532,7 @@ impl DataManager {
|
|||
let res = query_rows!(
|
||||
&conn,
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE replying_to = 0{}{}{} AND NOT context LIKE '%\"full_unlist\":true%' AND topic = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
"SELECT * FROM posts WHERE replying_to = 0{}{}{} AND NOT context LIKE '%\"full_unlist\":true%' AND topic = 0 ORDER BY created DESC LIMIT $1",
|
||||
if before_time > 0 {
|
||||
format!(" AND created < {before_time}")
|
||||
} else {
|
||||
|
@ -1535,12 +1549,12 @@ impl DataManager {
|
|||
""
|
||||
}
|
||||
),
|
||||
&[&(batch as i64), &((page * batch) as i64)],
|
||||
&[&(batch as i64)],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1556,7 +1570,6 @@ impl DataManager {
|
|||
batch: usize,
|
||||
page: usize,
|
||||
as_user: &Option<User>,
|
||||
before_time: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
// check if we should hide nsfw posts
|
||||
let mut hide_nsfw: bool = true;
|
||||
|
@ -1574,12 +1587,7 @@ impl DataManager {
|
|||
let res = query_rows!(
|
||||
&conn,
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE replying_to = 0{}{} AND NOT context LIKE '%\"full_unlist\":true%' AND NOT topic = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
if before_time > 0 {
|
||||
format!(" AND created < {before_time}")
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
"SELECT * FROM posts WHERE replying_to = 0{} AND NOT context LIKE '%\"full_unlist\":true%' AND NOT topic = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
if hide_nsfw {
|
||||
" AND NOT context LIKE '%\"is_nsfw\":true%'"
|
||||
} else {
|
||||
|
@ -1590,8 +1598,8 @@ impl DataManager {
|
|||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1602,12 +1610,12 @@ impl DataManager {
|
|||
/// # Arguments
|
||||
/// * `id` - the ID of the user
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
/// * `before` - the timestamp to pull posts before
|
||||
pub async fn get_posts_from_user_communities(
|
||||
&self,
|
||||
id: usize,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
before: usize,
|
||||
user: &User,
|
||||
) -> Result<Vec<Post>> {
|
||||
let memberships = self.get_memberships_by_owner(id).await?;
|
||||
|
@ -1635,20 +1643,25 @@ impl DataManager {
|
|||
let res = query_rows!(
|
||||
&conn,
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE (community = {} {query_string}){} AND NOT context LIKE '%\"full_unlist\":true%' AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
"SELECT * FROM posts WHERE (community = {} {query_string}){}{} AND NOT context LIKE '%\"full_unlist\":true%' AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $1",
|
||||
first.community,
|
||||
if hide_nsfw {
|
||||
" AND NOT context LIKE '%\"is_nsfw\":true%'"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if before > 0 {
|
||||
format!(" AND created < {before}")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
&[&(batch as i64), &((page * batch) as i64)],
|
||||
&[&(batch as i64)],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1659,12 +1672,12 @@ impl DataManager {
|
|||
/// # Arguments
|
||||
/// * `id` - the ID of the user
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
/// * `before` - the timestamp to pull posts before
|
||||
pub async fn get_posts_from_user_following(
|
||||
&self,
|
||||
id: usize,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
before: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
let following = self.get_userfollows_by_initiator_all(id).await?;
|
||||
let mut following = following.iter();
|
||||
|
@ -1688,15 +1701,20 @@ impl DataManager {
|
|||
let res = query_rows!(
|
||||
&conn,
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE (owner = {id} OR owner = {} {query_string}) AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
first.receiver
|
||||
"SELECT * FROM posts WHERE (owner = {id} OR owner = {} {query_string}) AND replying_to = 0 AND is_deleted = 0{} ORDER BY created DESC LIMIT $1",
|
||||
first.receiver,
|
||||
if before > 0 {
|
||||
format!(" AND created < {before}")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
&[&(batch as i64), &((page * batch) as i64)],
|
||||
&[&(batch as i64)],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1750,8 +1768,8 @@ impl DataManager {
|
|||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1830,8 +1848,15 @@ impl DataManager {
|
|||
));
|
||||
}
|
||||
|
||||
if stack.owner != data.owner && !stack.users.contains(&data.owner) {
|
||||
return Err(Error::NotAllowed);
|
||||
if !stack.is_locked || data.replying_to.is_some() {
|
||||
if stack.owner != data.owner && !stack.users.contains(&data.owner) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
} else {
|
||||
// only the owner can post in locked stacks UNLESS we're creating a reply
|
||||
if stack.owner != data.owner {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ impl DataManager {
|
|||
privacy: serde_json::from_str(&get!(x->5(String))).unwrap(),
|
||||
mode: serde_json::from_str(&get!(x->6(String))).unwrap(),
|
||||
sort: serde_json::from_str(&get!(x->7(String))).unwrap(),
|
||||
is_locked: get!(x->8(i32)) == 1,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +57,7 @@ impl DataManager {
|
|||
match stack.sort {
|
||||
StackSort::Created => {
|
||||
self.fill_posts_with_community(
|
||||
self.get_latest_posts(batch, page, &user, 0).await?,
|
||||
self.get_latest_posts(batch, &user, 0).await?,
|
||||
as_user_id,
|
||||
&ignore_users,
|
||||
user,
|
||||
|
@ -184,7 +185,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO stacks VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
"INSERT INTO stacks VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -194,6 +195,7 @@ impl DataManager {
|
|||
&serde_json::to_string(&data.privacy).unwrap(),
|
||||
&serde_json::to_string(&data.mode).unwrap(),
|
||||
&serde_json::to_string(&data.sort).unwrap(),
|
||||
&if data.is_locked { 1 } else { 0 },
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -207,6 +209,10 @@ impl DataManager {
|
|||
pub async fn delete_stack(&self, id: usize, user: &User) -> Result<()> {
|
||||
let stack = self.get_stack_by_id(id).await?;
|
||||
|
||||
if stack.is_locked {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
// check user permission
|
||||
if user.id != stack.owner && !user.permissions.check(FinePermission::MANAGE_STACKS) {
|
||||
return Err(Error::NotAllowed);
|
||||
|
|
|
@ -107,6 +107,12 @@ pub struct User {
|
|||
/// The time in which the user last consented to the site's policies.
|
||||
#[serde(default)]
|
||||
pub last_policy_consent: usize,
|
||||
/// The ID of the user's close friends stack.
|
||||
///
|
||||
/// The user's close friends stack is a circle stack which only allows the owner
|
||||
/// (the user) to post to it.
|
||||
#[serde(default)]
|
||||
pub close_friends_stack: usize,
|
||||
}
|
||||
|
||||
pub type UserConnections =
|
||||
|
@ -430,6 +436,7 @@ impl User {
|
|||
checkouts: Vec::new(),
|
||||
applied_configurations: Vec::new(),
|
||||
last_policy_consent: created,
|
||||
close_friends_stack: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,9 @@ pub struct UserStack {
|
|||
pub privacy: StackPrivacy,
|
||||
pub mode: StackMode,
|
||||
pub sort: StackSort,
|
||||
/// Locked stacks cannot be deleted or have their mode changed. Stacks cannot
|
||||
/// be locked after creation, and must be locked by the server.
|
||||
pub is_locked: bool,
|
||||
}
|
||||
|
||||
impl UserStack {
|
||||
|
@ -74,6 +77,7 @@ impl UserStack {
|
|||
privacy: StackPrivacy::default(),
|
||||
mode: StackMode::default(),
|
||||
sort: StackSort::default(),
|
||||
is_locked: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue