add: image proxy
add: mentions in posts TODO: audit log, reports, user mod panel
This commit is contained in:
parent
e183a01887
commit
3a8af17154
14 changed files with 308 additions and 33 deletions
|
@ -124,6 +124,14 @@ pub struct Config {
|
|||
/// The port to serve the server on.
|
||||
#[serde(default = "default_port")]
|
||||
pub port: u16,
|
||||
/// A list of hosts which cannot be proxied through the image proxy.
|
||||
///
|
||||
/// They will return the default banner image instead of proxying.
|
||||
///
|
||||
/// It is recommended to put the host of your own public server in this list in
|
||||
/// order to prevent a way too easy DOS.
|
||||
#[serde(default = "default_banned_hosts")]
|
||||
pub banned_hosts: Vec<String>,
|
||||
/// Database security.
|
||||
#[serde(default = "default_security")]
|
||||
pub security: SecurityConfig,
|
||||
|
@ -157,6 +165,10 @@ fn default_port() -> u16 {
|
|||
4118
|
||||
}
|
||||
|
||||
fn default_banned_hosts() -> Vec<String> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn default_security() -> SecurityConfig {
|
||||
SecurityConfig::default()
|
||||
}
|
||||
|
@ -193,6 +205,7 @@ impl Default for Config {
|
|||
description: default_description(),
|
||||
color: default_color(),
|
||||
port: default_port(),
|
||||
banned_hosts: default_banned_hosts(),
|
||||
database: default_database(),
|
||||
security: default_security(),
|
||||
dirs: default_dirs(),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::*;
|
||||
use crate::cache::Cache;
|
||||
use crate::model::auth::Notification;
|
||||
use crate::model::{
|
||||
Error, Result,
|
||||
auth::User,
|
||||
|
@ -284,7 +285,7 @@ impl DataManager {
|
|||
///
|
||||
/// # Arguments
|
||||
/// * `data` - a mock [`JournalEntry`] object to insert
|
||||
pub async fn create_post(&self, data: Post) -> Result<usize> {
|
||||
pub async fn create_post(&self, mut data: Post) -> Result<usize> {
|
||||
// check values
|
||||
if data.content.len() < 2 {
|
||||
return Err(Error::DataTooShort("content".to_string()));
|
||||
|
@ -312,30 +313,50 @@ impl DataManager {
|
|||
}
|
||||
}
|
||||
|
||||
// send mention notifications
|
||||
for username in User::parse_mentions(&data.content) {
|
||||
let user = self.get_user_by_username(&username).await?;
|
||||
self.create_notification(Notification::new(
|
||||
"You've been mentioned in a post!".to_string(),
|
||||
format!(
|
||||
"[Somebody](/api/v1/auth/profile/find/{}) mentioned you in their [post](/post/{}).",
|
||||
data.owner, data.id
|
||||
),
|
||||
user.id,
|
||||
))
|
||||
.await?;
|
||||
data.content = data.content.replace(
|
||||
&format!("@{username}"),
|
||||
&format!("[@{username}](/api/v1/auth/profile/find/{})", user.id),
|
||||
);
|
||||
}
|
||||
|
||||
// ...
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let replying_to_id = data.replying_to.unwrap_or(0).to_string();
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO posts VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
&[
|
||||
&Some(data.id.to_string()),
|
||||
&Some(data.created.to_string()),
|
||||
&Some(data.content),
|
||||
&Some(data.owner.to_string()),
|
||||
&Some(data.community.to_string()),
|
||||
&Some(serde_json::to_string(&data.context).unwrap()),
|
||||
&if let Some(id) = data.replying_to {
|
||||
Some(id.to_string())
|
||||
&Some(data.id.to_string().as_str()),
|
||||
&Some(data.created.to_string().as_str()),
|
||||
&Some(&data.content),
|
||||
&Some(data.owner.to_string().as_str()),
|
||||
&Some(data.community.to_string().as_str()),
|
||||
&Some(&serde_json::to_string(&data.context).unwrap()),
|
||||
&if replying_to_id != "0" {
|
||||
Some(replying_to_id.as_str())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
&Some(0.to_string()),
|
||||
&Some(0.to_string()),
|
||||
&Some(0.to_string())
|
||||
&Some(0.to_string().as_str()),
|
||||
&Some(0.to_string().as_str()),
|
||||
&Some(0.to_string().as_str())
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -346,6 +367,22 @@ impl DataManager {
|
|||
// incr comment count
|
||||
if let Some(id) = data.replying_to {
|
||||
self.incr_post_comments(id).await.unwrap();
|
||||
|
||||
// send notification
|
||||
let rt = self.get_post_by_id(id).await?;
|
||||
|
||||
if data.owner != rt.owner {
|
||||
let owner = self.get_user_by_id(rt.owner).await?;
|
||||
self.create_notification(Notification::new(
|
||||
"Your post has received a new comment!".to_string(),
|
||||
format!(
|
||||
"[@{}](/api/v1/auth/profile/find/{}) has commented on your [post](/post/{}).",
|
||||
owner.username, owner.id, rt.id
|
||||
),
|
||||
rt.owner,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// return
|
||||
|
|
|
@ -105,6 +105,49 @@ impl User {
|
|||
pub fn check_password(&self, against: String) -> bool {
|
||||
self.password == hash_salted(against, self.salt.clone())
|
||||
}
|
||||
|
||||
/// Parse user mentions in a given `input`.
|
||||
pub fn parse_mentions(input: &str) -> Vec<String> {
|
||||
// state
|
||||
let mut escape: bool = false;
|
||||
let mut at: bool = false;
|
||||
let mut buffer: String = String::new();
|
||||
let mut out = Vec::new();
|
||||
|
||||
// parse
|
||||
for char in input.chars() {
|
||||
if (char == '\\') && !escape {
|
||||
escape = true;
|
||||
}
|
||||
|
||||
if (char == '@') && !escape {
|
||||
at = true;
|
||||
continue; // don't push @
|
||||
}
|
||||
|
||||
if at {
|
||||
if (char == ' ') && !escape {
|
||||
// reached space, end @
|
||||
at = false;
|
||||
|
||||
if !out.contains(&buffer) {
|
||||
out.push(buffer);
|
||||
}
|
||||
|
||||
buffer = String::new();
|
||||
continue;
|
||||
}
|
||||
|
||||
// push mention text
|
||||
buffer.push(char);
|
||||
}
|
||||
|
||||
escape = false;
|
||||
}
|
||||
|
||||
// return
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
|
|
@ -28,8 +28,8 @@ impl Serialize for CommunityPermission {
|
|||
}
|
||||
}
|
||||
|
||||
struct JournalPermissionVisitor;
|
||||
impl<'de> Visitor<'de> for JournalPermissionVisitor {
|
||||
struct CommunityPermissionVisitor;
|
||||
impl<'de> Visitor<'de> for CommunityPermissionVisitor {
|
||||
type Value = CommunityPermission;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
|
@ -75,22 +75,22 @@ impl<'de> Deserialize<'de> for CommunityPermission {
|
|||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(JournalPermissionVisitor)
|
||||
deserializer.deserialize_any(CommunityPermissionVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommunityPermission {
|
||||
/// Join two [`JournalPermission`]s into a single `u32`.
|
||||
/// Join two [`CommunityPermission`]s into a single `u32`.
|
||||
pub fn join(lhs: CommunityPermission, rhs: CommunityPermission) -> CommunityPermission {
|
||||
lhs | rhs
|
||||
}
|
||||
|
||||
/// Check if the given `input` contains the given [`JournalPermission`].
|
||||
/// Check if the given `input` contains the given [`CommunityPermission`].
|
||||
pub fn check(self, permission: CommunityPermission) -> bool {
|
||||
if (self & CommunityPermission::ADMINISTRATOR) == CommunityPermission::ADMINISTRATOR {
|
||||
// has administrator permission, meaning everything else is automatically true
|
||||
return true;
|
||||
} else if (self & CommunityPermission::BANNED) == CommunityPermission::BANNED {
|
||||
} else if self.check_banned() {
|
||||
// has banned permission, meaning everything else is automatically false
|
||||
return false;
|
||||
}
|
||||
|
@ -98,15 +98,20 @@ impl CommunityPermission {
|
|||
(self & permission) == permission
|
||||
}
|
||||
|
||||
/// Check if the given [`JournalPermission`] qualifies as "Member" status.
|
||||
/// Check if the given [`CommunityPermission`] qualifies as "Member" status.
|
||||
pub fn check_member(self) -> bool {
|
||||
self.check(CommunityPermission::MEMBER)
|
||||
}
|
||||
|
||||
/// Check if the given [`JournalPermission`] qualifies as "Moderator" status.
|
||||
/// Check if the given [`CommunityPermission`] qualifies as "Moderator" status.
|
||||
pub fn check_moderator(self) -> bool {
|
||||
self.check(CommunityPermission::MANAGE_POSTS)
|
||||
}
|
||||
|
||||
/// Check if the given [`CommunityPermission`] qualifies as "Banned" status.
|
||||
pub fn check_banned(self) -> bool {
|
||||
(self & CommunityPermission::BANNED) == CommunityPermission::BANNED
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CommunityPermission {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue