add: anonymous questions
This commit is contained in:
parent
2266afde01
commit
3db7f2699c
34 changed files with 473 additions and 98 deletions
|
@ -9,7 +9,6 @@ use crate::model::{
|
|||
use crate::{auto_method, execute, get, query_row, params};
|
||||
use pathbufd::PathBufD;
|
||||
use std::fs::{exists, remove_file};
|
||||
use std::usize;
|
||||
use tetratto_shared::hash::{hash_salted, salt};
|
||||
use tetratto_shared::unix_epoch_timestamp;
|
||||
|
||||
|
@ -259,6 +258,16 @@ impl DataManager {
|
|||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"DELETE FROM ipblocks WHERE initiator = $1",
|
||||
&[&(id as i64)]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// delete reactions
|
||||
// reactions counts will remain the same :)
|
||||
let res = execute!(
|
||||
|
|
|
@ -27,6 +27,7 @@ impl DataManager {
|
|||
execute!(&conn, common::CREATE_TABLE_USER_WARNINGS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_REQUESTS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_QUESTIONS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_IPBLOCKS).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -338,7 +339,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_audit_log_entry(crate::model::moderation::AuditLogEntry::new(
|
||||
self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
|
||||
user.id,
|
||||
format!("invoked `{}` with x value `{x:?}`", stringify!($name)),
|
||||
))
|
||||
|
@ -493,7 +494,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_audit_log_entry(crate::model::moderation::AuditLogEntry::new(
|
||||
self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
|
||||
user.id,
|
||||
format!("invoked `{}` with x value `{id}`", stringify!($name)),
|
||||
))
|
||||
|
|
|
@ -12,3 +12,4 @@ pub const CREATE_TABLE_REPORTS: &str = include_str!("./sql/create_reports.sql");
|
|||
pub const CREATE_TABLE_USER_WARNINGS: &str = include_str!("./sql/create_user_warnings.sql");
|
||||
pub const CREATE_TABLE_REQUESTS: &str = include_str!("./sql/create_requests.sql");
|
||||
pub const CREATE_TABLE_QUESTIONS: &str = include_str!("./sql/create_questions.sql");
|
||||
pub const CREATE_TABLE_IPBLOCKS: &str = include_str!("./sql/create_ipblocks.sql");
|
||||
|
|
6
crates/core/src/database/drivers/sql/create_ipblocks.sql
Normal file
6
crates/core/src/database/drivers/sql/create_ipblocks.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE IF NOT EXISTS ipblocks (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
created BIGINT NOT NULL,
|
||||
initiator BIGINT NOT NULL,
|
||||
receiver TEXT NOT NULL
|
||||
)
|
|
@ -9,5 +9,8 @@ CREATE TABLE IF NOT EXISTS questions (
|
|||
community BIGINT NOT NULL,
|
||||
-- likes
|
||||
likes INT NOT NULL,
|
||||
dislikes INT NOT NULL
|
||||
dislikes INT NOT NULL,
|
||||
-- ...
|
||||
context TEXT NOT NULL,
|
||||
ip TEXT NOT NULL
|
||||
)
|
||||
|
|
133
crates/core/src/database/ipblocks.rs
Normal file
133
crates/core/src/database/ipblocks.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use super::*;
|
||||
use crate::cache::Cache;
|
||||
use crate::model::{Error, Result, auth::User, auth::IpBlock, permissions::FinePermission};
|
||||
use crate::{auto_method, execute, get, query_row, params};
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
use rusqlite::Row;
|
||||
|
||||
#[cfg(feature = "postgres")]
|
||||
use tokio_postgres::Row;
|
||||
|
||||
impl DataManager {
|
||||
/// Get a [`UserBlock`] from an SQL row.
|
||||
pub(crate) fn get_ipblock_from_row(
|
||||
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||
#[cfg(feature = "postgres")] x: &Row,
|
||||
) -> IpBlock {
|
||||
IpBlock {
|
||||
id: get!(x->0(i64)) as usize,
|
||||
created: get!(x->1(i64)) as usize,
|
||||
initiator: get!(x->2(i64)) as usize,
|
||||
receiver: get!(x->3(String)),
|
||||
}
|
||||
}
|
||||
|
||||
auto_method!(get_ipblock_by_id()@get_ipblock_from_row -> "SELECT * FROM ipblocks WHERE id = $1" --name="ip block" --returns=IpBlock --cache-key-tmpl="atto.ipblock:{}");
|
||||
|
||||
/// Get a user block by `initiator` and `receiver` (in that order).
|
||||
pub async fn get_ipblock_by_initiator_receiver(
|
||||
&self,
|
||||
initiator: usize,
|
||||
receiver: &str,
|
||||
) -> Result<IpBlock> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_row!(
|
||||
&conn,
|
||||
"SELECT * FROM ipblocks WHERE initiator = $1 AND receiver = $2",
|
||||
params![&(initiator as i64), &receiver],
|
||||
|x| { Ok(Self::get_ipblock_from_row(x)) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("user block".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Get a user block by `receiver` and `initiator` (in that order).
|
||||
pub async fn get_ipblock_by_receiver_initiator(
|
||||
&self,
|
||||
receiver: &str,
|
||||
initiator: usize,
|
||||
) -> Result<IpBlock> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_row!(
|
||||
&conn,
|
||||
"SELECT * FROM ipblocks WHERE receiver = $1 AND initiator = $2",
|
||||
params![&receiver, &(initiator as i64)],
|
||||
|x| { Ok(Self::get_ipblock_from_row(x)) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("user block".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Create a new user block in the database.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - a mock [`UserBlock`] object to insert
|
||||
pub async fn create_ipblock(&self, data: IpBlock) -> Result<()> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO ipblocks VALUES ($1, $2, $3, $4)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
&(data.initiator as i64),
|
||||
&data.receiver
|
||||
]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// return
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_ipblock(&self, id: usize, user: User) -> Result<()> {
|
||||
let block = self.get_ipblock_by_id(id).await?;
|
||||
|
||||
if user.id != block.initiator {
|
||||
// only the initiator (or moderators) can delete user blocks!
|
||||
if !user.permissions.check(FinePermission::MANAGE_FOLLOWS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(&conn, "DELETE FROM ipblocks WHERE id = $1", &[&(id as i64)]);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
self.2.remove(format!("atto.ipblock:{}", id)).await;
|
||||
|
||||
// return
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ mod common;
|
|||
mod communities;
|
||||
mod drivers;
|
||||
mod ipbans;
|
||||
mod ipblocks;
|
||||
mod memberships;
|
||||
mod notifications;
|
||||
mod posts;
|
||||
|
|
|
@ -105,7 +105,12 @@ impl DataManager {
|
|||
pub async fn get_post_question(&self, post: &Post) -> Result<Option<(Question, User)>> {
|
||||
if post.context.answering != 0 {
|
||||
let question = self.get_question_by_id(post.context.answering).await?;
|
||||
let user = self.get_user_by_id_with_void(question.owner).await?;
|
||||
let user = if question.owner == 0 {
|
||||
User::anonymous()
|
||||
} else {
|
||||
self.get_user_by_id_with_void(question.owner).await?
|
||||
};
|
||||
|
||||
Ok(Some((question, user)))
|
||||
} else {
|
||||
Ok(None)
|
||||
|
@ -563,7 +568,7 @@ impl DataManager {
|
|||
.get_membership_by_owner_community(uid, community.id)
|
||||
.await
|
||||
{
|
||||
Ok(m) => !(!m.role.check_member()),
|
||||
Ok(m) => m.role.check_member(),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
@ -630,7 +635,7 @@ impl DataManager {
|
|||
|
||||
// create notification for question owner
|
||||
// (if the current user isn't the owner)
|
||||
if question.owner != data.owner {
|
||||
if (question.owner != data.owner) && (question.owner != 0) {
|
||||
self.create_notification(Notification::new(
|
||||
"Your question has received a new answer!".to_string(),
|
||||
format!(
|
||||
|
@ -682,9 +687,10 @@ impl DataManager {
|
|||
}
|
||||
|
||||
// check blocked status
|
||||
if let Ok(_) = self
|
||||
if self
|
||||
.get_userblock_by_initiator_receiver(rt.owner, data.owner)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
@ -703,9 +709,10 @@ impl DataManager {
|
|||
}
|
||||
|
||||
// check blocked status
|
||||
if let Ok(_) = self
|
||||
if self
|
||||
.get_userblock_by_initiator_receiver(rt.owner, data.owner)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ impl DataManager {
|
|||
dislikes: get!(x->9(i32)) as isize,
|
||||
// ...
|
||||
context: serde_json::from_str(&get!(x->10(String))).unwrap(),
|
||||
ip: get!(x->11(String)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +54,12 @@ impl DataManager {
|
|||
if let Some(ua) = seen_users.get(&question.owner) {
|
||||
out.push((question, ua.to_owned()));
|
||||
} else {
|
||||
let user = self.get_user_by_id_with_void(question.owner).await?;
|
||||
let user = if question.owner == 0 {
|
||||
User::anonymous()
|
||||
} else {
|
||||
self.get_user_by_id_with_void(question.owner).await?
|
||||
};
|
||||
|
||||
seen_users.insert(question.owner, user.clone());
|
||||
out.push((question, user));
|
||||
}
|
||||
|
@ -311,6 +317,15 @@ impl DataManager {
|
|||
if !receiver.settings.enable_questions {
|
||||
return Err(Error::QuestionsDisabled);
|
||||
}
|
||||
|
||||
// check for ip block
|
||||
if self
|
||||
.get_ipblock_by_initiator_receiver(receiver.id, &data.ip)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let receiver = self.get_user_by_id(data.receiver).await?;
|
||||
|
@ -318,6 +333,19 @@ impl DataManager {
|
|||
if !receiver.settings.enable_questions {
|
||||
return Err(Error::QuestionsDisabled);
|
||||
}
|
||||
|
||||
if !receiver.settings.allow_anonymous_questions && data.owner == 0 {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
// check for ip block
|
||||
if self
|
||||
.get_ipblock_by_initiator_receiver(receiver.id, &data.ip)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
@ -328,7 +356,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO questions VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
||||
"INSERT INTO questions VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -340,7 +368,8 @@ impl DataManager {
|
|||
&(data.community as i64),
|
||||
&0_i32,
|
||||
&0_i32,
|
||||
&serde_json::to_string(&data.context).unwrap()
|
||||
&serde_json::to_string(&data.context).unwrap(),
|
||||
&data.ip
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -130,10 +130,8 @@ impl DataManager {
|
|||
.get_request_by_id_linked_asset(id, linked_asset)
|
||||
.await?;
|
||||
|
||||
if !force {
|
||||
if user.id != y.owner && !user.permissions.check(FinePermission::MANAGE_REQUESTS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
if !force && user.id != y.owner && !user.permissions.check(FinePermission::MANAGE_REQUESTS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
let conn = match self.connect().await {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue