add: reposts/quotes pages

add: repost notification
This commit is contained in:
trisua 2025-04-23 16:46:13 -04:00
parent 41250ef7ed
commit 276f25a496
17 changed files with 601 additions and 50 deletions

View file

@ -121,6 +121,7 @@ impl DataManager {
pub async fn fill_posts(
&self,
posts: Vec<Post>,
ignore_users: &Vec<usize>,
) -> Result<Vec<(Post, User, Option<(User, Post)>, Option<(Question, User)>)>> {
let mut out: Vec<(Post, User, Option<(User, Post)>, Option<(Question, User)>)> = Vec::new();
@ -128,6 +129,10 @@ impl DataManager {
for post in posts {
let owner = post.owner;
if ignore_users.contains(&owner) {
continue;
}
if let Some(user) = users.get(&owner) {
out.push((
post.clone(),
@ -155,6 +160,7 @@ impl DataManager {
&self,
posts: Vec<Post>,
user_id: usize,
ignore_users: &Vec<usize>,
) -> Result<
Vec<(
Post,
@ -177,6 +183,11 @@ impl DataManager {
for post in posts {
let owner = post.owner;
if ignore_users.contains(&owner) {
continue;
}
let community = post.community;
if let Some((user, community)) = seen_before.get(&(owner, community)) {
@ -402,6 +413,82 @@ impl DataManager {
Ok(res.unwrap())
}
/// Get all quoting posts by the post their quoting.
///
/// Requires that the post has content. See [`Self::get_reposts_by_quoting`]
/// for the no-content version.
///
/// # Arguments
/// * `id` - the ID of the post that is being quoted
/// * `batch` - the limit of posts in each page
/// * `page` - the page number
pub async fn get_quoting_posts_by_quoting(
&self,
id: usize,
batch: usize,
page: usize,
) -> Result<Vec<Post>> {
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = query_rows!(
&conn,
"SELECT * FROM posts WHERE NOT content = '' AND context LIKE $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
params![
&format!("%\"reposting\":{id}%"),
&(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()));
}
Ok(res.unwrap())
}
/// Get all quoting posts by the post their quoting.
///
/// Requires that the post has no content. See [`Self::get_quoting_posts_by_quoting`]
/// for the content-required version.
///
/// # Arguments
/// * `id` - the ID of the post that is being quoted
/// * `batch` - the limit of posts in each page
/// * `page` - the page number
pub async fn get_reposts_by_quoting(
&self,
id: usize,
batch: usize,
page: usize,
) -> Result<Vec<Post>> {
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = query_rows!(
&conn,
"SELECT * FROM posts WHERE content = '' AND context LIKE $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
params![
&format!("%\"reposting\":{id}%"),
&(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()));
}
Ok(res.unwrap())
}
/// Get posts from all communities, sorted by likes.
///
/// # Arguments
@ -694,6 +781,25 @@ impl DataManager {
{
return Err(Error::NotAllowed);
}
// send notification
// this would look better if rustfmt didn't give up on this line
if owner.id != rt.owner {
self.create_notification(
Notification::new(
format!(
"[@{}](/api/v1/auth/user/find/{}) has [quoted](/post/{}) your [post](/post/{})",
owner.username,
owner.id,
data.id,
rt.id
),
format!("\"{}\"", data.content),
rt.owner
)
)
.await?;
}
}
// check if the post we're replying to allows commments
@ -729,8 +835,8 @@ impl DataManager {
self.create_notification(Notification::new(
"You've been mentioned in a post!".to_string(),
format!(
"[Somebody](/api/v1/auth/user/find/{}) mentioned you in their [post](/post/{}).",
data.owner, data.id
"[@{}](/api/v1/auth/user/find/{}) has mentioned you in their [post](/post/{}).",
owner.username, owner.id, data.id
),
user.id,
))

View file

@ -46,11 +46,19 @@ impl DataManager {
auto_method!(get_question_by_id()@get_question_from_row -> "SELECT * FROM questions WHERE id = $1" --name="question" --returns=Question --cache-key-tmpl="atto.question:{}");
/// Fill the given vector of questions with their owner as well.
pub async fn fill_questions(&self, questions: Vec<Question>) -> Result<Vec<(Question, User)>> {
pub async fn fill_questions(
&self,
questions: Vec<Question>,
ignore_users: &Vec<usize>,
) -> Result<Vec<(Question, User)>> {
let mut out: Vec<(Question, User)> = Vec::new();
let mut seen_users: HashMap<usize, User> = HashMap::new();
for question in questions {
if ignore_users.contains(&question.owner) {
continue;
}
if let Some(ua) = seen_users.get(&question.owner) {
out.push((question, ua.to_owned()));
} else {

View file

@ -130,7 +130,8 @@ impl DataManager {
.get_request_by_id_linked_asset(id, linked_asset)
.await?;
if !force && user.id != y.owner && !user.permissions.check(FinePermission::MANAGE_REQUESTS) {
if !force && user.id != y.owner && !user.permissions.check(FinePermission::MANAGE_REQUESTS)
{
return Err(Error::NotAllowed);
}

View file

@ -1,7 +1,7 @@
use super::*;
use crate::cache::Cache;
use crate::model::{Error, Result, auth::User, auth::UserBlock, permissions::FinePermission};
use crate::{auto_method, execute, get, query_row, params};
use crate::{auto_method, execute, get, params, query_row, query_rows};
#[cfg(feature = "sqlite")]
use rusqlite::Row;
@ -75,6 +75,35 @@ impl DataManager {
Ok(res.unwrap())
}
/// Get the receiver of all user blocks for the given `initiator`.
pub async fn get_userblocks_receivers(&self, initiator: usize) -> Vec<usize> {
let conn = match self.connect().await {
Ok(c) => c,
Err(_) => return Vec::new(),
};
let res = query_rows!(
&conn,
"SELECT * FROM userblocks WHERE receiver = $1",
&[&(initiator as i64)],
|x| { Self::get_userblock_from_row(x) }
);
if res.is_err() {
return Vec::new();
}
// get receivers
let mut out: Vec<usize> = Vec::new();
for b in res.unwrap() {
out.push(b.receiver);
}
// return
out
}
/// Create a new user block in the database.
///
/// # Arguments