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}