add: audit log, reports
add: theme preference setting
This commit is contained in:
parent
b2df2739a7
commit
d3d0c41334
38 changed files with 925 additions and 169 deletions
|
@ -3,7 +3,7 @@ use crate::cache::Cache;
|
|||
use crate::model::{
|
||||
Error, Result, auth::User, moderation::AuditLogEntry, permissions::FinePermission,
|
||||
};
|
||||
use crate::{auto_method, execute, get, query_row};
|
||||
use crate::{auto_method, execute, get, query_row, query_rows};
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
use rusqlite::Row;
|
||||
|
@ -13,7 +13,7 @@ use tokio_postgres::Row;
|
|||
|
||||
impl DataManager {
|
||||
/// Get an [`AuditLogEntry`] from an SQL row.
|
||||
pub(crate) fn get_auditlog_entry_from_row(
|
||||
pub(crate) fn get_audit_log_entry_from_row(
|
||||
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||
#[cfg(feature = "postgres")] x: &Row,
|
||||
) -> AuditLogEntry {
|
||||
|
@ -25,13 +25,42 @@ impl DataManager {
|
|||
}
|
||||
}
|
||||
|
||||
auto_method!(get_auditlog_entry_by_id(usize)@get_auditlog_entry_from_row -> "SELECT * FROM auditlog WHERE id = $1" --name="audit log entry" --returns=AuditLogEntry --cache-key-tmpl="atto.auditlog:{}");
|
||||
auto_method!(get_audit_log_entry_by_id(usize)@get_audit_log_entry_from_row -> "SELECT * FROM audit_log WHERE id = $1" --name="audit log entry" --returns=AuditLogEntry --cache-key-tmpl="atto.audit_log:{}");
|
||||
|
||||
/// Get all audit log entries (paginated).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `batch` - the limit of items in each page
|
||||
/// * `page` - the page number
|
||||
pub async fn get_audit_log_entries(
|
||||
&self,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
) -> Result<Vec<AuditLogEntry>> {
|
||||
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 audit_log ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
&[&(batch as isize), &((page * batch) as isize)],
|
||||
|x| { Self::get_audit_log_entry_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("audit log entry".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Create a new audit log entry in the database.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - a mock [`AuditLogEntry`] object to insert
|
||||
pub async fn create_auditlog_entry(&self, data: AuditLogEntry) -> Result<()> {
|
||||
pub async fn create_audit_log_entry(&self, data: AuditLogEntry) -> Result<()> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
|
@ -39,7 +68,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO auditlog VALUES ($1, $2, $3, $4)",
|
||||
"INSERT INTO audit_log VALUES ($1, $2, $3, $4)",
|
||||
&[
|
||||
&data.id.to_string().as_str(),
|
||||
&data.created.to_string().as_str(),
|
||||
|
@ -56,7 +85,7 @@ impl DataManager {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_auditlog_entry(&self, id: usize, user: User) -> Result<()> {
|
||||
pub async fn delete_audit_log_entry(&self, id: usize, user: User) -> Result<()> {
|
||||
if !user.permissions.check(FinePermission::MANAGE_AUDITLOG) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
@ -68,7 +97,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"DELETE FROM auditlog WHERE id = $1",
|
||||
"DELETE FROM audit_log WHERE id = $1",
|
||||
&[&id.to_string()]
|
||||
);
|
||||
|
||||
|
@ -76,7 +105,7 @@ impl DataManager {
|
|||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
self.2.remove(format!("atto.auditlog:{}", id)).await;
|
||||
self.2.remove(format!("atto.audit_log:{}", id)).await;
|
||||
|
||||
// return
|
||||
Ok(())
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::*;
|
||||
use crate::cache::Cache;
|
||||
use crate::model::moderation::AuditLogEntry;
|
||||
use crate::model::{
|
||||
Error, Result,
|
||||
auth::{Token, User, UserSettings},
|
||||
|
@ -262,6 +263,17 @@ impl DataManager {
|
|||
|
||||
self.cache_clear_user(&other_user).await;
|
||||
|
||||
// create audit log entry
|
||||
self.create_audit_log_entry(AuditLogEntry::new(
|
||||
user.id,
|
||||
format!(
|
||||
"invoked `update_user_verified_status` with x value `{}` and y value `{}`",
|
||||
other_user.id, x
|
||||
),
|
||||
))
|
||||
.await?;
|
||||
|
||||
// ...
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -326,6 +338,67 @@ impl DataManager {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_user_role(
|
||||
&self,
|
||||
id: usize,
|
||||
role: FinePermission,
|
||||
user: User,
|
||||
) -> Result<()> {
|
||||
// check permission
|
||||
if !user.permissions.check(FinePermission::MANAGE_USERS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
let other_user = self.get_user_by_id(id).await?;
|
||||
|
||||
if other_user.permissions.check_manager() && !user.permissions.check_admin() {
|
||||
return Err(Error::MiscError(
|
||||
"Cannot manage the role of other managers".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if other_user.permissions == user.permissions {
|
||||
return Err(Error::MiscError(
|
||||
"Cannot manage users of equal level to you".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// ...
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"UPDATE users SET permissions = $1 WHERE id = $2",
|
||||
&[
|
||||
&(role.bits()).to_string().as_str(),
|
||||
&id.to_string().as_str()
|
||||
]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
self.cache_clear_user(&other_user).await;
|
||||
|
||||
// create audit log entry
|
||||
self.create_audit_log_entry(AuditLogEntry::new(
|
||||
user.id,
|
||||
format!(
|
||||
"invoked `update_user_role` with x value `{}` and y value `{}`",
|
||||
other_user.id,
|
||||
role.bits()
|
||||
),
|
||||
))
|
||||
.await?;
|
||||
|
||||
// ...
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cache_clear_user(&self, user: &User) {
|
||||
self.2.remove(format!("atto.user:{}", user.id)).await;
|
||||
self.2.remove(format!("atto.user:{}", user.username)).await;
|
||||
|
|
|
@ -22,7 +22,7 @@ impl DataManager {
|
|||
execute!(&conn, common::CREATE_TABLE_USERFOLLOWS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_USERBLOCKS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_IPBANS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_AUDITLOG).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_AUDIT_LOG).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_REPORTS).unwrap();
|
||||
|
||||
Ok(())
|
||||
|
@ -139,7 +139,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_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)),
|
||||
))
|
||||
|
@ -169,7 +169,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_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)),
|
||||
))
|
||||
|
@ -201,7 +201,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_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)),
|
||||
))
|
||||
|
@ -232,7 +232,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_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)),
|
||||
))
|
||||
|
@ -265,7 +265,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_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), id),
|
||||
))
|
||||
|
@ -300,7 +300,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_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)),
|
||||
))
|
||||
|
@ -455,7 +455,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_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)),
|
||||
))
|
||||
|
@ -488,7 +488,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_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)),
|
||||
))
|
||||
|
@ -546,7 +546,7 @@ macro_rules! auto_method {
|
|||
if !user.permissions.check(FinePermission::$permission) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_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)),
|
||||
))
|
||||
|
|
|
@ -224,7 +224,7 @@ impl DataManager {
|
|||
if !user.permissions.check(FinePermission::MANAGE_COMMUNITIES) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_entry(crate::model::moderation::AuditLogEntry::new(
|
||||
self.create_audit_log_entry(crate::model::moderation::AuditLogEntry::new(
|
||||
user.id,
|
||||
format!("invoked `delete_community` with x value `{id}`"),
|
||||
))
|
||||
|
|
|
@ -7,5 +7,5 @@ pub const CREATE_TABLE_NOTIFICATIONS: &str = include_str!("./sql/create_notifica
|
|||
pub const CREATE_TABLE_USERFOLLOWS: &str = include_str!("./sql/create_userfollows.sql");
|
||||
pub const CREATE_TABLE_USERBLOCKS: &str = include_str!("./sql/create_userblocks.sql");
|
||||
pub const CREATE_TABLE_IPBANS: &str = include_str!("./sql/create_ipbans.sql");
|
||||
pub const CREATE_TABLE_AUDITLOG: &str = include_str!("./sql/create_auditlog.sql");
|
||||
pub const CREATE_TABLE_AUDIT_LOG: &str = include_str!("./sql/create_audit_log.sql");
|
||||
pub const CREATE_TABLE_REPORTS: &str = include_str!("./sql/create_reports.sql");
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
created INTEGER NOT NULL,
|
||||
owner INTEGER NOT NULL,
|
||||
content TEXT NOT NULL
|
||||
)
|
|
@ -1,6 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS auditlog (
|
||||
ip TEXT NOT NULL,
|
||||
created INTEGER NOT NULL PRIMARY KEY,
|
||||
moderator TEXT NOT NULL,
|
||||
content TEXT NOT NULL
|
||||
)
|
|
@ -1,7 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS reports (
|
||||
ip TEXT NOT NULL,
|
||||
created INTEGER NOT NULL PRIMARY KEY,
|
||||
owner TEXT NOT NULL,
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
created INTEGER NOT NULL,
|
||||
owner INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
asset INTEGER NOT NULL,
|
||||
asset_type TEXT NOT NULL
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::*;
|
||||
use crate::cache::Cache;
|
||||
use crate::model::moderation::AuditLogEntry;
|
||||
use crate::model::{Error, Result, auth::IpBan, auth::User, permissions::FinePermission};
|
||||
use crate::{auto_method, execute, get, query_row};
|
||||
|
||||
|
@ -57,11 +58,18 @@ impl DataManager {
|
|||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// create audit log entry
|
||||
self.create_audit_log_entry(AuditLogEntry::new(
|
||||
user.id,
|
||||
format!("invoked `create_ipban` with x value `{}`", data.ip),
|
||||
))
|
||||
.await?;
|
||||
|
||||
// return
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_ipban(&self, id: usize, user: User) -> Result<()> {
|
||||
pub async fn delete_ipban(&self, ip: &str, user: User) -> Result<()> {
|
||||
// ONLY moderators can manage ip bans
|
||||
if !user.permissions.check(FinePermission::MANAGE_BANS) {
|
||||
return Err(Error::NotAllowed);
|
||||
|
@ -72,17 +80,20 @@ impl DataManager {
|
|||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"DELETE FROM ipbans WHERE id = $1",
|
||||
&[&id.to_string()]
|
||||
);
|
||||
let res = execute!(&conn, "DELETE FROM ipbans WHERE ip = $1", &[ip]);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
self.2.remove(format!("atto.ipban:{}", id)).await;
|
||||
self.2.remove(format!("atto.ipban:{}", ip)).await;
|
||||
|
||||
// create audit log entry
|
||||
self.create_audit_log_entry(AuditLogEntry::new(
|
||||
user.id,
|
||||
format!("invoked `delete_ipban` with x value `{ip}`"),
|
||||
))
|
||||
.await?;
|
||||
|
||||
// return
|
||||
Ok(())
|
||||
|
|
|
@ -331,6 +331,31 @@ 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?;
|
||||
|
||||
if rt.context.comments_enabled == false {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
|
@ -364,27 +389,6 @@ impl DataManager {
|
|||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// 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
|
||||
Ok(data.id)
|
||||
}
|
||||
|
@ -396,7 +400,7 @@ impl DataManager {
|
|||
if !user.permissions.check(FinePermission::MANAGE_POSTS) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_auditlog_entry(crate::model::moderation::AuditLogEntry::new(
|
||||
self.create_audit_log_entry(crate::model::moderation::AuditLogEntry::new(
|
||||
user.id,
|
||||
format!("invoked `delete_post` with x value `{id}`"),
|
||||
))
|
||||
|
|
|
@ -144,6 +144,9 @@ impl DataManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
AssetType::User => {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
};
|
||||
|
||||
// return
|
||||
|
@ -200,6 +203,9 @@ impl DataManager {
|
|||
return Err(e);
|
||||
}
|
||||
}
|
||||
AssetType::User => {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
};
|
||||
|
||||
// return
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use super::*;
|
||||
use crate::cache::Cache;
|
||||
use crate::model::moderation::AuditLogEntry;
|
||||
use crate::model::{Error, Result, auth::User, moderation::Report, permissions::FinePermission};
|
||||
use crate::{auto_method, execute, get, query_row};
|
||||
use crate::{auto_method, execute, get, query_row, query_rows};
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
use rusqlite::Row;
|
||||
|
@ -27,6 +28,31 @@ impl DataManager {
|
|||
|
||||
auto_method!(get_report_by_id(usize)@get_report_from_row -> "SELECT * FROM reports WHERE id = $1" --name="report" --returns=Report --cache-key-tmpl="atto.reports:{}");
|
||||
|
||||
/// Get all reports (paginated).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `batch` - the limit of items in each page
|
||||
/// * `page` - the page number
|
||||
pub async fn get_reports(&self, batch: usize, page: usize) -> Result<Vec<Report>> {
|
||||
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 reports ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
&[&(batch as isize), &((page * batch) as isize)],
|
||||
|x| { Self::get_report_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("report".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Create a new report in the database.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -80,6 +106,13 @@ impl DataManager {
|
|||
|
||||
self.2.remove(format!("atto.report:{}", id)).await;
|
||||
|
||||
// create audit log entry
|
||||
self.create_audit_log_entry(AuditLogEntry::new(
|
||||
user.id,
|
||||
format!("invoked `delete_report` with x value `{id}`"),
|
||||
))
|
||||
.await?;
|
||||
|
||||
// return
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue