omni_orchestrator/schemas/v1/db/queries/audit_log.rs
1use anyhow::Context;
2use sqlx::{MySql, Pool};
3
4use libomni::types::db::v1 as types;
5use types::audit_log::AuditLog;
6
7/// Creates a new audit log entry in the system.
8///
9/// This function records an action performed within the system, tracking who
10/// performed the action, what organization they belong to, what action was
11/// performed, on what type of resource, and which specific resource was affected.
12/// It serves as a critical component for maintaining accountability and tracking
13/// system changes.
14///
15/// # Arguments
16///
17/// * `pool` - Database connection pool for executing the query
18/// * `user_id` - Optional ID of the user who performed the action (None for system actions)
19/// * `org_id` - Optional ID of the organization in which the action occurred
20/// * `action` - Description of the action performed (e.g., "create", "update", "delete")
21/// * `resource_type` - Type of resource affected (e.g., "app", "deployment", "user")
22/// * `resource_id` - Optional identifier of the specific resource affected
23///
24/// # Returns
25///
26/// * `Ok(AuditLog)` - Successfully created audit log entry with all database-assigned fields
27/// * `Err(anyhow::Error)` - Failed to create the audit log entry
28///
29/// # Examples
30///
31/// ```
32/// // Log a user creating an application
33/// create_audit_log(
34/// &pool,
35/// Some(user_id),
36/// Some(org_id),
37/// "create",
38/// "app",
39/// Some(app_id.to_string())
40/// ).await?;
41///
42/// // Log a system maintenance action
43/// create_audit_log(
44/// &pool,
45/// None,
46/// None,
47/// "maintenance",
48/// "system",
49/// None
50/// ).await?;
51/// ```
52pub async fn create_audit_log(
53 pool: &Pool<MySql>,
54 user_id: Option<i64>,
55 org_id: Option<i64>,
56 action: &str,
57 resource_type: &str,
58 resource_id: Option<String>,
59) -> anyhow::Result<AuditLog> {
60 let audit_log = sqlx::query_as::<_, AuditLog>(
61 r#"
62 INSERT INTO audit_logs (
63 user_id, org_id, action, resource_type, resource_id
64 ) VALUES (?, ?, ?, ?, ?)
65 "#,
66 )
67 .bind(user_id)
68 .bind(org_id)
69 .bind(action)
70 .bind(resource_type)
71 .bind(resource_id)
72 .fetch_one(pool)
73 .await
74 .context("Failed to create audit log")?;
75
76 Ok(audit_log)
77}
78
79/// Retrieves a paginated list of audit logs ordered by creation time.
80///
81/// This function fetches audit logs with pagination support, allowing for
82/// efficient browsing through potentially large numbers of log entries.
83/// The most recent logs are returned first.
84///
85/// # Arguments
86///
87/// * `pool` - Database connection pool for executing the query
88/// * `limit` - Maximum number of logs to retrieve in this page
89/// * `page` - Zero-based page offset (e.g., 0 for first page, 1 for second page)
90///
91/// # Returns
92///
93/// * `Ok(Vec<AuditLog>)` - Successfully retrieved audit logs for the requested page
94/// * `Err(anyhow::Error)` - Failed to fetch the audit logs
95///
96/// # Pagination
97///
98/// The function calculates the appropriate OFFSET based on the page and limit values.
99/// For example, with a limit of 10:
100/// - page 0 → entries 0-9
101/// - page 1 → entries 10-19
102/// - page 2 → entries 20-29
103///
104/// # Notes
105///
106/// The page parameter in this function represents the page number, not the offset value.
107/// The actual offset is calculated internally as `page * limit`.
108pub async fn list_audit_logs_paginated(
109 pool: &Pool<MySql>,
110 limit: i64,
111 page: i64,
112) -> anyhow::Result<Vec<AuditLog>> {
113 let audit_logs = sqlx::query_as::<_, AuditLog>(
114 r#"
115 SELECT * FROM audit_logs
116 ORDER BY created_at DESC
117 LIMIT ? OFFSET ?
118 "#,
119 )
120 .bind(limit)
121 .bind(page)
122 .fetch_all(pool)
123 .await
124 .context("Failed to fetch audit logs")?;
125
126 Ok(audit_logs)
127}
128
129/// Count the total number of audit logs in the system.
130///
131/// This function provides a simple count of all audit logs stored in the database.
132/// It is useful for understanding the volume of logged actions and can be
133/// used in conjunction with pagination to inform users about the
134/// total number of available logs.
135///
136/// # Arguments
137///
138/// * `pool` - Database connection pool for executing the query
139///
140/// # Returns
141///
142/// * `Ok(i64)` - Total number of audit logs in the system
143pub async fn count_audit_logs(
144 pool: &Pool<MySql>,
145) -> anyhow::Result<i64> {
146 let count = sqlx::query_scalar::<_, i64>(
147 r#"
148 SELECT COUNT(*) FROM audit_logs
149 "#,
150 )
151 .fetch_one(pool)
152 .await
153 .context("Failed to count audit logs")?;
154
155 Ok(count)
156}
157
158/// Retrieves audit logs for a specific resource.
159///
160/// This function fetches audit logs related to a particular resource, identified
161/// by its type and ID. This is useful for viewing the history of actions performed
162/// on a specific entity in the system.
163///
164/// # Arguments
165///
166/// * `pool` - Database connection pool for executing the query
167/// * `resource_type` - Type of resource to filter by (e.g., "app", "deployment")
168/// * `resource_id` - Identifier of the specific resource to get logs for
169/// * `limit` - Maximum number of logs to retrieve
170///
171/// # Returns
172///
173/// * `Ok(Vec<AuditLog>)` - Successfully retrieved audit logs for the resource
174/// * `Err(anyhow::Error)` - Failed to fetch the resource audit logs
175///
176/// # Ordering
177///
178/// Results are ordered by creation time in descending order, so the most
179/// recent actions appear first in the returned list.
180///
181/// # Example
182///
183/// ```
184/// // Get the most recent 20 actions on an application
185/// let app_history = get_audit_logs_by_resource(
186/// &pool,
187/// "app",
188/// &app_id.to_string(),
189/// 20
190/// ).await?;
191/// ```
192pub async fn get_audit_logs_by_resource(
193 pool: &Pool<MySql>,
194 resource_type: &str,
195 resource_id: &str,
196 limit: i64,
197) -> anyhow::Result<Vec<AuditLog>> {
198 let audit_logs = sqlx::query_as::<_, AuditLog>(
199 r#"
200 SELECT * FROM audit_logs
201 WHERE resource_type = ? AND resource_id = ?
202 ORDER BY created_at DESC
203 LIMIT ?
204 "#,
205 )
206 .bind(resource_type)
207 .bind(resource_id)
208 .bind(limit)
209 .fetch_all(pool)
210 .await
211 .context("Failed to fetch resource audit logs")?;
212
213 Ok(audit_logs)
214}
215
216/// Retrieves audit logs for actions performed by a specific user.
217///
218/// This function fetches audit logs for activities carried out by a particular user,
219/// identified by their user ID. This is useful for monitoring user activities,
220/// security auditing, or providing users with a history of their actions.
221///
222/// # Arguments
223///
224/// * `pool` - Database connection pool for executing the query
225/// * `user_id` - Unique identifier of the user whose logs to retrieve
226/// * `limit` - Maximum number of logs to retrieve
227///
228/// # Returns
229///
230/// * `Ok(Vec<AuditLog>)` - Successfully retrieved audit logs for the user
231/// * `Err(anyhow::Error)` - Failed to fetch the user audit logs
232///
233/// # Use Cases
234///
235/// Common use cases for this function include:
236/// - Security monitoring for suspicious user activity
237/// - User activity history displays in admin interfaces
238/// - Accountability tracking for administrative actions
239/// - Providing users with a history of their own actions in the system
240///
241/// # Ordering
242///
243/// Results are ordered by creation time in descending order, so the most
244/// recent actions appear first in the returned list.
245pub async fn get_user_audit_logs(
246 pool: &Pool<MySql>,
247 user_id: i64,
248 limit: i64,
249) -> anyhow::Result<Vec<AuditLog>> {
250 let audit_logs = sqlx::query_as::<_, AuditLog>(
251 r#"
252 SELECT * FROM audit_logs
253 WHERE user_id = ?
254 ORDER BY created_at DESC
255 LIMIT ?
256 "#,
257 )
258 .bind(user_id)
259 .bind(limit)
260 .fetch_all(pool)
261 .await
262 .context("Failed to fetch user audit logs")?;
263
264 Ok(audit_logs)
265}
266
267/// Retrieves audit logs for actions within a specific organization.
268///
269/// This function fetches audit logs for all activities that occurred within
270/// a particular organization, identified by its organization ID. This is useful
271/// for organizational-level auditing, compliance reporting, and activity monitoring.
272///
273/// # Arguments
274///
275/// * `pool` - Database connection pool for executing the query
276/// * `org_id` - Unique identifier of the organization whose logs to retrieve
277/// * `limit` - Maximum number of logs to retrieve
278///
279/// # Returns
280///
281/// * `Ok(Vec<AuditLog>)` - Successfully retrieved audit logs for the organization
282/// * `Err(anyhow::Error)` - Failed to fetch the organization audit logs
283///
284/// # Use Cases
285///
286/// Common use cases for this function include:
287/// - Compliance reporting for organization activities
288/// - Administrative oversight of organization-wide actions
289/// - Organizational security auditing
290/// - Activity dashboards for organization managers
291///
292/// # Ordering
293///
294/// Results are ordered by creation time in descending order, so the most
295/// recent actions appear first in the returned list.
296///
297/// # Note
298///
299/// This function retrieves all actions associated with the organization,
300/// regardless of which user performed them. This includes system actions
301/// that affect the organization but weren't initiated by a specific user.
302pub async fn get_org_audit_logs(
303 pool: &Pool<MySql>,
304 org_id: i64,
305 limit: i64,
306) -> anyhow::Result<Vec<AuditLog>> {
307 let audit_logs = sqlx::query_as::<_, AuditLog>(
308 r#"
309 SELECT * FROM audit_logs
310 WHERE org_id = ?
311 ORDER BY created_at DESC
312 LIMIT ?
313 "#,
314 )
315 .bind(org_id)
316 .bind(limit)
317 .fetch_all(pool)
318 .await
319 .context("Failed to fetch organization audit logs")?;
320
321 Ok(audit_logs)
322}
323
324/// Retrieves audit logs for a given app_id.
325///
326/// This function fetches audit logs related to a specific application,
327/// identified by its app_id. This is useful for viewing the history
328/// of actions performed on a specific application in the system.
329///
330/// # Arguments
331///
332/// * `pool` - Database connection pool for executing the query
333/// * `app_id` - Identifier of the specific application to get logs for
334/// * `page` - Zero-based page offset (e.g., 0 for first page, 1 for second page)
335/// * `per_page` - Maximum number of logs to retrieve
336///
337/// # Returns
338///
339/// * `Ok(Vec<AuditLog>)` - Successfully retrieved audit logs for the application
340pub async fn get_audit_logs_by_app(
341 pool: &Pool<MySql>,
342 app_id: i64,
343 page: i64,
344 per_page: i64,
345) -> anyhow::Result<Vec<AuditLog>> {
346 let audit_logs = sqlx::query_as::<_, AuditLog>(
347 r#"
348 SELECT * FROM audit_logs
349 WHERE app_id = ?
350 ORDER BY created_at DESC
351 LIMIT ? OFFSET ?
352 "#,
353 )
354 .bind(app_id)
355 .bind(per_page)
356 .bind(page * per_page)
357 .fetch_all(pool)
358 .await
359 .context("Failed to fetch app audit logs")?;
360
361 Ok(audit_logs)
362}
363
364/// Retrieves the number of audit logs for a given app_id.
365///
366/// This function counts the number of audit logs related to a specific application,
367/// identified by its app_id. This is useful for understanding the volume of logged
368/// actions for a specific application.
369///
370/// # Arguments
371/// * `pool` - Database connection pool for executing the query
372/// * `app_id` - Identifier of the specific application to count logs for
373///
374/// # Returns
375///
376/// * `Ok(i64)` - Total number of audit logs for the application
377pub async fn count_audit_logs_by_app(
378 pool: &Pool<MySql>,
379 app_id: i64,
380) -> anyhow::Result<i64> {
381 let count = sqlx::query_scalar::<_, i64>(
382 r#"
383 SELECT COUNT(*) FROM audit_logs
384 WHERE app_id = ?
385 "#,
386 )
387 .bind(app_id)
388 .fetch_one(pool)
389 .await
390 .context("Failed to count app audit logs")?;
391
392 Ok(count)
393}