use std::{collections::HashSet, time::Duration};
use iso8601_timestamp::Timestamp;
use revolt_config::config;
use ulid::Ulid;
use crate::{Database, PartialChannel, PartialMember, PartialRole, PartialServer, User, PartialEmoji};
use revolt_models::v0;
use revolt_permissions::OverrideField;
use revolt_result::Result;
auto_derived!(
pub struct AuditLogEntry {
#[serde(rename = "_id")]
pub id: String,
pub expires_at: Timestamp,
pub server: String,
pub reason: Option<String>,
pub user: String,
pub target: Option<String>,
pub action: AuditLogEntryAction,
}
#[serde(tag = "type")]
#[allow(clippy::large_enum_variant)]
pub enum AuditLogEntryAction {
MessageDelete {
author: String,
channel: String,
},
MessageBulkDelete {
channel: String,
count: usize,
},
MessagePin {
message: String,
author: String,
channel: String,
},
MessageUnpin {
message: String,
author: String,
channel: String,
},
BanCreate {
user: String,
},
BanDelete {
user: String,
},
ChannelCreate {
channel: String,
name: String,
},
ChannelEdit {
channel: String,
before: PartialChannel,
after: PartialChannel,
},
ChannelRolePermissionsEdit {
channel: String,
role: String,
permissions: OverrideField,
},
ChannelDelete {
channel: String,
name: String,
},
MemberEdit {
user: String,
before: PartialMember,
after: PartialMember,
},
MemberKick {
user: String,
},
ServerEdit {
before: PartialServer,
after: PartialServer,
},
RoleEdit {
role: String,
before: PartialRole,
after: PartialRole,
},
RoleCreate {
role: String,
name: String,
},
RoleDelete {
role: String,
name: String,
},
RolesReorder {
before: Vec<String>,
after: Vec<String>,
},
InviteCreate {
invite: String,
channel: String,
},
InviteDelete {
invite: String,
channel: String,
},
WebhookCreate {
webhook: String,
name: String,
channel: String,
},
WebhookDelete {
webhook: String,
name: String,
channel: String,
},
EmojiCreate {
emoji: String,
name: String,
},
EmojiUpdate {
emoji: String,
before: PartialEmoji,
after: PartialEmoji,
},
EmojiDelete {
emoji: String,
name: String,
},
}
pub struct AuditLogQuery {
pub user: Option<String>,
pub target: Option<String>,
pub r#type: Option<Vec<String>>,
pub before: Option<String>,
pub after: Option<String>,
pub limit: i64,
}
);
impl AuditLogEntryAction {
pub async fn insert<R: Into<Option<String>>>(
self,
db: &Database,
server: String,
reason: R,
user: String,
target: Option<String>,
) -> AuditLogEntry {
let config = config().await;
let id = Ulid::new();
let expires_at = id
.datetime()
.checked_add(Duration::from_secs(config.api.audit_logs.expires_after))
.unwrap()
.into();
let entry = AuditLogEntry {
id: id.to_string(),
expires_at,
server,
reason: reason.into(),
user,
target,
action: self,
};
#[cfg(not(test))]
tokio::task::spawn({
let db = db.clone();
let entry = entry.clone();
async move { revolt_config::report_internal_error!(db.insert_audit_log_entry(&entry).await) }
});
#[cfg(test)]
db.insert_audit_log_entry(&entry).await.unwrap();
entry
}
}
impl AuditLogEntry {
pub async fn with_users(
db: &Database,
server_id: &str,
user: &User,
entries: &[Self],
) -> Result<(Vec<v0::User>, Vec<v0::Member>)> {
let mut user_ids = HashSet::new();
for entry in entries {
user_ids.insert(entry.user.clone());
match &entry.action {
AuditLogEntryAction::MessageDelete { author, .. } => {
user_ids.insert(author.clone());
}
AuditLogEntryAction::BanCreate { user } => {
user_ids.insert(user.clone());
}
AuditLogEntryAction::BanDelete { user } => {
user_ids.insert(user.clone());
}
AuditLogEntryAction::ChannelCreate { .. } => {}
AuditLogEntryAction::MemberEdit { user, .. } => {
user_ids.insert(user.clone());
}
AuditLogEntryAction::MemberKick { user } => {
user_ids.insert(user.clone());
}
AuditLogEntryAction::MessagePin { author, .. } => {
user_ids.insert(author.clone());
}
AuditLogEntryAction::MessageUnpin { author, .. } => {
user_ids.insert(author.clone());
}
AuditLogEntryAction::ServerEdit { .. } => {}
AuditLogEntryAction::RoleEdit { .. } => {}
AuditLogEntryAction::RoleCreate { .. } => {}
AuditLogEntryAction::RoleDelete { .. } => {}
AuditLogEntryAction::RolesReorder { .. } => {}
AuditLogEntryAction::MessageBulkDelete { .. } => {}
AuditLogEntryAction::ChannelEdit { .. } => {}
AuditLogEntryAction::ChannelRolePermissionsEdit { .. } => {}
AuditLogEntryAction::ChannelDelete { .. } => {}
AuditLogEntryAction::InviteCreate { .. } => {}
AuditLogEntryAction::InviteDelete { .. } => {}
AuditLogEntryAction::WebhookCreate { .. } => {}
AuditLogEntryAction::WebhookDelete { .. } => {}
AuditLogEntryAction::EmojiCreate { .. } => {}
AuditLogEntryAction::EmojiUpdate { .. } => {}
AuditLogEntryAction::EmojiDelete { .. } => {}
};
}
let user_ids = user_ids.into_iter().collect::<Vec<_>>();
let users = User::fetch_many_ids_as_mutuals(db, user, &user_ids).await?;
let members = db
.fetch_members(server_id, &user_ids)
.await?
.into_iter()
.map(Into::into)
.collect();
Ok((users, members))
}
}