clickup_cli/commands/
audit_log.rs1use crate::client::ClickUpClient;
2use crate::commands::auth::resolve_token;
3use crate::commands::workspace::resolve_workspace;
4use crate::error::CliError;
5use crate::output::OutputConfig;
6use crate::Cli;
7use clap::Subcommand;
8
9#[derive(Subcommand)]
10pub enum AuditLogCommands {
11 Query {
13 #[arg(long)]
15 applicability: String,
16 #[arg(long = "event-type")]
18 event_type: Option<String>,
19 #[arg(long = "event-status")]
21 event_status: Option<String>,
22 #[arg(long = "user-id")]
24 user_id: Vec<String>,
25 #[arg(long = "user-email")]
27 user_email: Vec<String>,
28 #[arg(long)]
30 start_time: Option<i64>,
31 #[arg(long)]
33 end_time: Option<i64>,
34 #[arg(long)]
36 page_rows: Option<i64>,
37 #[arg(long)]
39 page_timestamp: Option<i64>,
40 #[arg(long)]
42 page_direction: Option<String>,
43 },
44}
45
46const AUDIT_LOG_FIELDS: &[&str] = &["id", "eventType", "userId", "createdAt"];
47
48pub async fn execute(command: AuditLogCommands, cli: &Cli) -> Result<(), CliError> {
49 let token = resolve_token(cli)?;
50 let client = ClickUpClient::new(&token, cli.timeout)?;
51 let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
52
53 match command {
54 AuditLogCommands::Query {
55 applicability,
56 event_type,
57 event_status,
58 user_id,
59 user_email,
60 start_time,
61 end_time,
62 page_rows,
63 page_timestamp,
64 page_direction,
65 } => {
66 let team_id = resolve_workspace(cli)?;
67 let path = format!("/v3/workspaces/{}/auditlogs", team_id);
68
69 let page_direction_owned = page_direction.clone();
70
71 let mut extra_pagination = serde_json::Map::new();
75 if let Some(n) = page_rows {
76 extra_pagination.insert("pageRows".into(), serde_json::Value::Number(n.into()));
77 }
78 if let Some(d) = page_direction_owned {
79 extra_pagination.insert("pageDirection".into(), serde_json::Value::String(d));
80 }
81
82 let logs = crate::commands::pagination::walk_body(
83 cli,
84 &client,
85 &path,
86 &["data", "audit_logs"],
87 || {
88 let mut body = serde_json::json!({ "applicability": applicability });
89 let mut filter = serde_json::Map::new();
90 if let Some(t) = &event_type {
91 filter.insert("eventType".into(), serde_json::json!(t));
92 }
93 if let Some(s) = &event_status {
94 filter.insert("eventStatus".into(), serde_json::json!(s));
95 }
96 if !user_id.is_empty() {
97 filter.insert(
98 "userId".into(),
99 serde_json::Value::Array(
100 user_id
101 .iter()
102 .cloned()
103 .map(serde_json::Value::String)
104 .collect(),
105 ),
106 );
107 }
108 if !user_email.is_empty() {
109 filter.insert(
110 "userEmail".into(),
111 serde_json::Value::Array(
112 user_email
113 .iter()
114 .cloned()
115 .map(serde_json::Value::String)
116 .collect(),
117 ),
118 );
119 }
120 if let Some(s) = start_time {
121 filter.insert("startTime".into(), serde_json::json!(s));
122 }
123 if let Some(e) = end_time {
124 filter.insert("endTime".into(), serde_json::json!(e));
125 }
126 if !filter.is_empty() {
127 body["filter"] = serde_json::Value::Object(filter);
128 }
129 body
130 },
131 extra_pagination,
132 page_timestamp,
133 |item| {
134 for key in ["eventTime", "timestamp", "date"] {
135 if let Some(v) = item.get(key) {
136 if let Some(n) = v.as_i64() {
137 return Some(n);
138 }
139 if let Some(s) = v.as_str() {
140 if let Ok(n) = s.parse::<i64>() {
141 return Some(n);
142 }
143 }
144 }
145 }
146 None
147 },
148 )
149 .await?;
150
151 if cli.output == "json" {
152 println!("{}", serde_json::to_string_pretty(&logs).unwrap());
153 return Ok(());
154 }
155
156 output.print_items(&logs, AUDIT_LOG_FIELDS, "id");
157 Ok(())
158 }
159 }
160}