omni_orchestrator/schemas/v1/db/queries/
audit_log.rs

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