revolt_database/models/audit_logs/
model.rs1use std::{collections::HashSet, time::Duration};
2
3use iso8601_timestamp::Timestamp;
4use revolt_config::config;
5use ulid::Ulid;
6
7use crate::{Database, PartialChannel, PartialMember, PartialRole, PartialServer, User, PartialEmoji};
8use revolt_models::v0;
9use revolt_permissions::OverrideField;
10use revolt_result::Result;
11
12auto_derived!(
13 pub struct AuditLogEntry {
15 #[serde(rename = "_id")]
17 pub id: String,
18
19 pub expires_at: Timestamp,
23
24 pub server: String,
26 pub reason: Option<String>,
28 pub user: String,
30 pub target: Option<String>,
32 pub action: AuditLogEntryAction,
34 }
35
36 #[serde(tag = "type")]
38 #[allow(clippy::large_enum_variant)]
39 pub enum AuditLogEntryAction {
40 MessageDelete {
41 author: String,
42 channel: String,
43 },
44 MessageBulkDelete {
45 channel: String,
46 count: usize,
47 },
48 MessagePin {
49 message: String,
50 author: String,
51 channel: String,
52 },
53 MessageUnpin {
54 message: String,
55 author: String,
56 channel: String,
57 },
58 BanCreate {
59 user: String,
60 },
61 BanDelete {
62 user: String,
63 },
64 ChannelCreate {
65 channel: String,
66 name: String,
67 },
68 ChannelEdit {
69 channel: String,
70 before: PartialChannel,
71 after: PartialChannel,
72 },
73 ChannelRolePermissionsEdit {
74 channel: String,
75 role: String,
76 permissions: OverrideField,
77 },
78 ChannelDelete {
79 channel: String,
80 name: String,
81 },
82 MemberEdit {
83 user: String,
84 before: PartialMember,
85 after: PartialMember,
86 },
87 MemberKick {
88 user: String,
89 },
90 ServerEdit {
91 before: PartialServer,
92 after: PartialServer,
93 },
94 RoleEdit {
95 role: String,
96 before: PartialRole,
97 after: PartialRole,
98 },
99 RoleCreate {
100 role: String,
101 name: String,
102 },
103 RoleDelete {
104 role: String,
105 name: String,
106 },
107 RolesReorder {
108 before: Vec<String>,
109 after: Vec<String>,
110 },
111 InviteCreate {
112 invite: String,
113 channel: String,
114 },
115 InviteDelete {
116 invite: String,
117 channel: String,
118 },
119 WebhookCreate {
120 webhook: String,
121 name: String,
122 channel: String,
123 },
124 WebhookDelete {
125 webhook: String,
126 name: String,
127 channel: String,
128 },
129 EmojiCreate {
130 emoji: String,
131 name: String,
132 },
133 EmojiUpdate {
134 emoji: String,
135 before: PartialEmoji,
136 after: PartialEmoji,
137 },
138 EmojiDelete {
139 emoji: String,
140 name: String,
141 },
142 }
143
144 pub struct AuditLogQuery {
146 pub user: Option<String>,
148 pub target: Option<String>,
150 pub r#type: Option<Vec<String>>,
152 pub before: Option<String>,
154 pub after: Option<String>,
156 pub limit: i64,
158 }
159);
160
161impl AuditLogEntryAction {
162 pub async fn insert<R: Into<Option<String>>>(
165 self,
166 db: &Database,
167 server: String,
168 reason: R,
169 user: String,
170 target: Option<String>,
171 ) -> AuditLogEntry {
172 let config = config().await;
173
174 let id = Ulid::new();
175 let expires_at = id
176 .datetime()
177 .checked_add(Duration::from_secs(config.api.audit_logs.expires_after))
178 .unwrap()
179 .into();
180
181 let entry = AuditLogEntry {
182 id: id.to_string(),
183 expires_at,
184 server,
185 reason: reason.into(),
186 user,
187 target,
188 action: self,
189 };
190
191 #[cfg(not(test))]
194 tokio::task::spawn({
195 let db = db.clone();
196 let entry = entry.clone();
197
198 async move { revolt_config::report_internal_error!(db.insert_audit_log_entry(&entry).await) }
199 });
200
201 #[cfg(test)]
202 db.insert_audit_log_entry(&entry).await.unwrap();
203
204 entry
205 }
206}
207
208impl AuditLogEntry {
209 pub async fn with_users(
211 db: &Database,
212 server_id: &str,
213 user: &User,
214 entries: &[Self],
215 ) -> Result<(Vec<v0::User>, Vec<v0::Member>)> {
216 let mut user_ids = HashSet::new();
217
218 for entry in entries {
219 user_ids.insert(entry.user.clone());
220
221 match &entry.action {
222 AuditLogEntryAction::MessageDelete { author, .. } => {
223 user_ids.insert(author.clone());
224 }
225 AuditLogEntryAction::BanCreate { user } => {
226 user_ids.insert(user.clone());
227 }
228 AuditLogEntryAction::BanDelete { user } => {
229 user_ids.insert(user.clone());
230 }
231 AuditLogEntryAction::ChannelCreate { .. } => {}
232 AuditLogEntryAction::MemberEdit { user, .. } => {
233 user_ids.insert(user.clone());
234 }
235 AuditLogEntryAction::MemberKick { user } => {
236 user_ids.insert(user.clone());
237 }
238 AuditLogEntryAction::MessagePin { author, .. } => {
239 user_ids.insert(author.clone());
240 }
241 AuditLogEntryAction::MessageUnpin { author, .. } => {
242 user_ids.insert(author.clone());
243 }
244 AuditLogEntryAction::ServerEdit { .. } => {}
245 AuditLogEntryAction::RoleEdit { .. } => {}
246 AuditLogEntryAction::RoleCreate { .. } => {}
247 AuditLogEntryAction::RoleDelete { .. } => {}
248 AuditLogEntryAction::RolesReorder { .. } => {}
249 AuditLogEntryAction::MessageBulkDelete { .. } => {}
250 AuditLogEntryAction::ChannelEdit { .. } => {}
251 AuditLogEntryAction::ChannelRolePermissionsEdit { .. } => {}
252 AuditLogEntryAction::ChannelDelete { .. } => {}
253 AuditLogEntryAction::InviteCreate { .. } => {}
254 AuditLogEntryAction::InviteDelete { .. } => {}
255 AuditLogEntryAction::WebhookCreate { .. } => {}
256 AuditLogEntryAction::WebhookDelete { .. } => {}
257 AuditLogEntryAction::EmojiCreate { .. } => {}
258 AuditLogEntryAction::EmojiUpdate { .. } => {}
259 AuditLogEntryAction::EmojiDelete { .. } => {}
260 };
261 }
262
263 let user_ids = user_ids.into_iter().collect::<Vec<_>>();
264
265 let users = User::fetch_many_ids_as_mutuals(db, user, &user_ids).await?;
266 let members = db
267 .fetch_members(server_id, &user_ids)
268 .await?
269 .into_iter()
270 .map(Into::into)
271 .collect();
272
273 Ok((users, members))
274 }
275}