headless_lms_server/controllers/main_frontend/credit_registration_admin/
audit.rs1use headless_lms_models::credit_registration_admin_actions::{
8 self, CreditRegistrationAdminAction, CreditRegistrationAdminActionFilters,
9 CreditRegistrationAdminActionListRow, CreditRegistrationAdminActionTarget,
10};
11use headless_lms_models::credit_registrations::CreditRegistrationState;
12use utoipa::ToSchema;
13
14use crate::prelude::*;
15
16use super::{authorize_credit_registration_admin, one_or_many};
17
18#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
19pub struct CreditRegistrationAdminActionRow {
20 pub id: Uuid,
21 pub created_at: DateTime<Utc>,
22 pub action: CreditRegistrationAdminAction,
23 pub target_kind: CreditRegistrationAdminActionTarget,
24 pub target_id: Option<Uuid>,
27 pub target_phase: Option<String>,
28 pub actor_user_id: Uuid,
29 pub actor_first_name: Option<String>,
30 pub actor_last_name: Option<String>,
31 pub actor_email: Option<String>,
32 pub actor_role: String,
34 pub actor_course_id: Option<Uuid>,
36 pub course_name: Option<String>,
37 pub reason: Option<String>,
38 pub before_state: Option<CreditRegistrationState>,
39 pub after_state: Option<CreditRegistrationState>,
40 pub details: Option<serde_json::Value>,
41 pub affected_row_count: Option<i32>,
42}
43
44#[derive(Debug, Deserialize)]
45pub struct ListAdminActionsQuery {
46 page: Option<u32>,
47 limit: Option<u32>,
48 #[serde(default, deserialize_with = "one_or_many")]
49 action: Option<Vec<CreditRegistrationAdminAction>>,
50 actor_user_id: Option<Uuid>,
51 actor_role: Option<String>,
52 target_kind: Option<CreditRegistrationAdminActionTarget>,
53 target_id: Option<Uuid>,
54 target_phase: Option<String>,
55 course_id: Option<Uuid>,
56 from: Option<DateTime<Utc>>,
57 to: Option<DateTime<Utc>>,
58}
59
60#[instrument(skip(pool))]
68#[utoipa::path(
69 get,
70 path = "/audit",
71 operation_id = "listCreditRegistrationAdminActions",
72 tag = "credit-registration-admin",
73 params(
74 ("page" = Option<u32>, Query, description = "Page number, from 1"),
75 ("limit" = Option<u32>, Query, description = "Rows per page"),
76 ("action" = Option<Vec<CreditRegistrationAdminAction>>, Query, description = "Action kinds; repeat the parameter for several"),
77 ("actor_user_id" = Option<Uuid>, Query, description = "Who acted"),
78 ("actor_role" = Option<String>, Query, description = "global_admin or course_teacher"),
79 ("target_kind" = Option<CreditRegistrationAdminActionTarget>, Query, description = "What was acted on"),
80 ("target_id" = Option<Uuid>, Query, description = "One target row"),
81 ("target_phase" = Option<String>, Query, description = "One pipeline phase"),
82 ("course_id" = Option<Uuid>, Query, description = "Actions on this course, and actions its teachers took"),
83 ("from" = Option<DateTime<Utc>>, Query, description = "Taken at or after"),
84 ("to" = Option<DateTime<Utc>>, Query, description = "Taken at or before")
85 ),
86 responses(
87 (status = 200, description = "A page of the action log", body = Page<CreditRegistrationAdminActionRow>)
88 )
89)]
90pub async fn list_credit_registration_admin_actions(
91 user: AuthUser,
92 pool: web::Data<PgPool>,
93 query: web::Query<ListAdminActionsQuery>,
94) -> ControllerResult<web::Json<Page<CreditRegistrationAdminActionRow>>> {
95 let mut conn = pool.acquire().await?;
96 let token = authorize_credit_registration_admin(&mut conn, user.id).await?;
97
98 let pagination = parse_pagination(query.page, query.limit, 50)?;
99 let actor_role = non_empty(query.actor_role.as_deref());
100 let target_phase = non_empty(query.target_phase.as_deref());
101 let filters = CreditRegistrationAdminActionFilters {
102 actions: query.action.as_deref(),
103 actor_user_id: query.actor_user_id,
104 actor_role,
105 target_kind: query.target_kind,
106 target_id: query.target_id,
107 target_phase,
108 course_id: query.course_id,
109 from: query.from,
110 to: query.to,
111 };
112 let rows = credit_registration_admin_actions::get_page(
113 &mut conn,
114 &filters,
115 pagination.limit(),
116 pagination.offset(),
117 )
118 .await?;
119 let total_count = rows.first().map_or(0, |row| row.total_count);
120
121 token.authorized_ok(web::Json(Page::new(
122 pagination,
123 rows.into_iter().map(to_action_row).collect(),
124 total_count,
125 )))
126}
127
128fn to_action_row(row: CreditRegistrationAdminActionListRow) -> CreditRegistrationAdminActionRow {
129 CreditRegistrationAdminActionRow {
130 actor_first_name: row.actor_first_name,
131 actor_last_name: row.actor_last_name,
132 actor_email: row.actor_email,
133 course_name: row.course_name,
134 id: row.action.id,
135 created_at: row.action.created_at,
136 action: row.action.action,
137 target_kind: row.action.target_kind,
138 target_id: row.action.target_id,
139 target_phase: row.action.target_phase,
140 actor_user_id: row.action.actor_user_id,
141 actor_role: row.action.actor_role,
142 actor_course_id: row.action.actor_course_id,
143 reason: row.action.reason,
144 before_state: row.action.before_state,
145 after_state: row.action.after_state,
146 details: row.action.details,
147 affected_row_count: row.action.affected_row_count,
148 }
149}
150
151pub fn _add_routes(cfg: &mut ServiceConfig) {
152 cfg.route(
153 "/audit",
154 web::get().to(list_credit_registration_admin_actions),
155 );
156}