add: profile and full search

This commit is contained in:
trisua 2025-05-18 16:43:56 -04:00
parent b8b0ef7f21
commit 3e4ee8126a
52 changed files with 897 additions and 484 deletions

View file

@ -196,6 +196,21 @@ pub struct StripeConfig {
pub billing_portal_url: String,
}
/// Manuals config (search help, etc)
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ManualsConfig {
/// The page shown for help with search syntax.
pub search_help: String,
}
impl Default for ManualsConfig {
fn default() -> Self {
Self {
search_help: "".to_string(),
}
}
}
/// Configuration file
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Config {
@ -259,6 +274,9 @@ pub struct Config {
pub html_footer_path: String,
#[serde(default)]
pub stripe: Option<StripeConfig>,
/// The relative paths to manuals.
#[serde(default)]
pub manuals: ManualsConfig,
}
fn default_name() -> String {
@ -307,12 +325,15 @@ fn default_banned_usernames() -> Vec<String> {
"moderator".to_string(),
"api".to_string(),
"communities".to_string(),
"community".to_string(),
"notifs".to_string(),
"notification".to_string(),
"post".to_string(),
"void".to_string(),
"anonymous".to_string(),
"stacks".to_string(),
"stack".to_string(),
"search".to_string(),
]
}
@ -328,6 +349,10 @@ fn default_connections() -> ConnectionsConfig {
ConnectionsConfig::default()
}
fn default_manuals() -> ManualsConfig {
ManualsConfig::default()
}
impl Default for Config {
fn default() -> Self {
Self {
@ -348,6 +373,7 @@ impl Default for Config {
connections: default_connections(),
html_footer_path: String::new(),
stripe: None,
manuals: default_manuals(),
}
}
}

View file

@ -13,5 +13,6 @@ CREATE TABLE IF NOT EXISTS posts (
comment_count INT NOT NULL,
-- ...
uploads TEXT NOT NULL,
is_deleted INT NOT NULL
is_deleted INT NOT NULL,
tsvector_content tsvector GENERATED ALWAYS AS (to_tsvector ('english', coalesce(content, ''))) STORED
)

View file

@ -52,6 +52,37 @@ macro_rules! private_post_replying {
continue;
}
};
($post:ident, $replying_posts:ident, id=$user_id:ident, $data:ident) => {
// post owner is not following us
// check if we're the owner of the post the post is replying to
// all routes but 1 must lead to continue
if let Some(replying) = $post.replying_to {
if replying != 0 {
if let Some(post) = $replying_posts.get(&replying) {
// we've seen this post before
if post.owner != $user_id {
// we aren't the owner of this post,
// so we can't see their comment
continue;
}
} else {
// we haven't seen this post before
let post = $data.get_post_by_id(replying).await?;
if post.owner != $user_id {
continue;
}
$replying_posts.insert(post.id, post);
}
} else {
continue;
}
} else {
continue;
}
};
}
impl DataManager {
@ -317,6 +348,7 @@ impl DataManager {
let mut seen_before: HashMap<(usize, usize), (User, Community)> = HashMap::new();
let mut seen_user_follow_statuses: HashMap<(usize, usize), bool> = HashMap::new();
let mut replying_posts: HashMap<usize, Post> = HashMap::new();
for post in posts {
if post.is_deleted {
@ -355,9 +387,8 @@ impl DataManager {
if user_id != ua.id {
if let Some(is_following) = seen_user_follow_statuses.get(&(ua.id, user_id))
{
if !is_following && (ua.id != user_id) {
// post owner is not following us
continue;
if !is_following {
private_post_replying!(post, replying_posts, id = user_id, self);
}
} else {
if self
@ -367,7 +398,7 @@ impl DataManager {
{
// post owner is not following us
seen_user_follow_statuses.insert((ua.id, user_id), false);
continue;
private_post_replying!(post, replying_posts, id = user_id, self);
}
seen_user_follow_statuses.insert((ua.id, user_id), true);
@ -428,9 +459,9 @@ impl DataManager {
let res = query_rows!(
&conn,
&format!(
"SELECT * FROM posts WHERE owner = $1 AND replying_to = 0 AND NOT context LIKE '%\"is_profile_pinned\":true%' {} ORDER BY created DESC LIMIT $2 OFFSET $3",
"SELECT * FROM posts WHERE owner = $1 AND replying_to = 0 AND NOT (context::json->>'is_profile_pinned')::boolean {} ORDER BY created DESC LIMIT $2 OFFSET $3",
if hide_nsfw {
"AND NOT context LIKE '%\"is_nsfw\":true%'"
"AND NOT (context::json->>'is_nsfw')::boolean"
} else {
""
}
@ -446,6 +477,101 @@ impl DataManager {
Ok(res.unwrap())
}
/// Get all posts from the given user (searched).
///
/// # Arguments
/// * `id` - the ID of the user the requested posts belong to
/// * `batch` - the limit of posts in each page
/// * `page` - the page number
/// * `text_query` - the search query
/// * `user` - the user who is viewing the posts
pub async fn get_posts_by_user_searched(
&self,
id: usize,
batch: usize,
page: usize,
text_query: &str,
user: &Option<&User>,
) -> Result<Vec<Post>> {
let other_user = self.get_user_by_id(id).await?;
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
// check if we should hide nsfw posts
let mut hide_nsfw: bool = true;
if let Some(ua) = user {
if ua.id == other_user.id {
hide_nsfw = false
}
}
if other_user.settings.private_profile {
hide_nsfw = false;
}
// ...
let res = query_rows!(
&conn,
&format!(
"SELECT * FROM posts WHERE owner = $1 AND tsvector_content @@ to_tsquery($2) AND replying_to = 0 AND NOT (context::json->>'is_profile_pinned')::boolean {} ORDER BY created DESC LIMIT $3 OFFSET $4",
if hide_nsfw {
"AND NOT (context::json->>'is_nsfw')::boolean"
} else {
""
}
),
params![
&(id as i64),
&text_query,
&(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 post (searched).
///
/// # Arguments
/// * `batch` - the limit of posts in each page
/// * `page` - the page number
/// * `text_query` - the search query
pub async fn get_posts_searched(
&self,
batch: usize,
page: usize,
text_query: &str,
) -> 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 tsvector_content @@ to_tsquery($1) AND replying_to = 0 AND NOT (context::json->>'is_profile_pinned')::boolean ORDER BY created DESC LIMIT $2 OFFSET $3",
params![&text_query, &(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 posts from the given user with the given tag (from most recent).
///
/// # Arguments
@ -487,7 +613,7 @@ impl DataManager {
&format!(
"SELECT * FROM posts WHERE owner = $1 AND context::json->>'tags' LIKE $2 {} ORDER BY created DESC LIMIT $3 OFFSET $4",
if hide_nsfw {
"AND NOT context LIKE '%\"is_nsfw\":true%'"
"AND NOT (context::json->>'is_nsfw')::boolean"
} else {
""
}