1mod boards;
2mod cards;
3mod create;
4mod current;
5mod delete;
6mod disable_login;
7mod enable_login;
8mod get;
9mod list;
10mod take_ownership;
11mod token;
12mod update;
13
14#[cfg(test)]
15mod tests;
16
17use clap::{Args, Subcommand};
18
19use crate::{
20 client::{UserRecord, WekanClientFactory},
21 command_result::{
22 CommandSuccess, UserBoardMembership, UserDetail, UserDialogOptionsDetail, UserEmail,
23 UserOrganization, UserProfileDetail, UserProfileNotificationDetail,
24 UserProfileStarredPageDetail, UserSessionData, UserTeam, UserWorkspaceNodeDetail,
25 },
26 credentials::{CredentialStore, SecretInputProvider},
27 error::AppError,
28 input::ConfirmationProvider,
29};
30
31use super::client_error::map_client_error;
32
33#[derive(Debug, Args)]
34pub struct UserArgs {
35 #[command(subcommand)]
36 pub command: UserCommand,
37}
38
39#[derive(Debug, Subcommand)]
40pub enum UserCommand {
41 Token(token::TokenArgs),
43 Current(current::CurrentArgs),
45
46 Cards(cards::CardsArgs),
48
49 List(list::ListArgs),
51
52 Get(get::GetArgs),
54
55 Update(update::UpdateArgs),
57
58 Create(create::CreateArgs),
60
61 Boards(boards::BoardsArgs),
63
64 TakeOwnership(take_ownership::TakeOwnershipArgs),
66
67 DisableLogin(disable_login::DisableLoginArgs),
69
70 EnableLogin(enable_login::EnableLoginArgs),
72
73 Delete(delete::DeleteArgs),
75}
76
77pub(crate) async fn dispatch(
78 command: UserCommand,
79 client_factory: &WekanClientFactory,
80 credential_store: &dyn CredentialStore,
81 secret_input: &dyn SecretInputProvider,
82 confirmation: &dyn ConfirmationProvider,
83) -> Result<CommandSuccess, AppError> {
84 match command {
85 UserCommand::Token(args) => {
86 token::dispatch(
87 args.command,
88 client_factory,
89 credential_store,
90 secret_input,
91 confirmation,
92 )
93 .await
94 }
95 UserCommand::Current(args) => {
96 current::execute(args, client_factory, credential_store).await
97 }
98 UserCommand::Cards(args) => cards::execute(args, client_factory, credential_store).await,
99 UserCommand::List(args) => list::execute(args, client_factory, credential_store).await,
100 UserCommand::Get(args) => get::execute(args, client_factory, credential_store).await,
101 UserCommand::Update(args) => update::execute(args, client_factory, credential_store).await,
102 UserCommand::Create(args) => {
103 create::execute(args, client_factory, credential_store, secret_input).await
104 }
105 UserCommand::Boards(args) => boards::execute(args, client_factory, credential_store).await,
106 UserCommand::TakeOwnership(args) => {
107 take_ownership::execute(args, client_factory, credential_store, confirmation).await
108 }
109 UserCommand::DisableLogin(args) => {
110 disable_login::execute(args, client_factory, credential_store, confirmation).await
111 }
112 UserCommand::EnableLogin(args) => {
113 enable_login::execute(args, client_factory, credential_store).await
114 }
115 UserCommand::Delete(args) => {
116 delete::execute(args, client_factory, credential_store, confirmation).await
117 }
118 }
119}
120
121fn user_detail(user: UserRecord) -> UserDetail {
122 UserDetail {
123 passkeys: user.passkeys,
124 sqrl: user.sqrl,
125 web_push_subscriptions: user.web_push_subscriptions,
126 ntfy: user.ntfy,
127 notification_delivery_pending: user.notification_delivery_pending,
128
129 notification_delivery_revision: user.notification_delivery_revision,
130 feature_preview: user.feature_preview,
131 inactivity_clock_start: user.inactivity_clock_start,
132 login_disabled_reason: user.login_disabled_reason,
133 anonymized: user.anonymized,
134 anonymized_at: user.anonymized_at,
135
136 user_id: user.user_id,
137 username: user.username,
138 full_name: user.profile.fullname.clone(),
139 profile: Box::new(user_profile(*user.profile)),
140 created_through_api: user.created_through_api,
141 heartbeat: user.heartbeat,
142 session_data: user.session_data.map(|data| UserSessionData {
143 total_hits: data.total_hits,
144 }),
145 import_usernames: user.import_usernames,
146 emails: user
147 .emails
148 .into_iter()
149 .map(|email| UserEmail {
150 address: email.address,
151 verified: email.verified,
152 })
153 .collect(),
154 is_admin: user.is_admin,
155 login_disabled: user.login_disabled,
156 authentication_method: user.authentication_method,
157 created_at: user.created_at,
158 modified_at: user.modified_at,
159 last_connection_date: user.last_connection_date,
160 organizations: user
161 .orgs
162 .into_iter()
163 .map(|organization| UserOrganization {
164 organization_id: organization.org_id,
165 display_name: organization.org_display_name,
166 is_admin: organization.is_admin,
167 })
168 .collect(),
169 teams: user
170 .teams
171 .into_iter()
172 .map(|team| UserTeam {
173 team_id: team.team_id,
174 display_name: team.team_display_name,
175 })
176 .collect(),
177 boards: user
178 .boards
179 .into_iter()
180 .map(|board| UserBoardMembership {
181 granted_by: board.granted_by,
182 custom_role_id: board.custom_role_id,
183 board_id: board.board_id,
184 is_active: board.is_active,
185 is_admin: board.is_admin,
186 is_no_comments: board.is_no_comments,
187 is_comment_only: board.is_comment_only,
188 is_worker: board.is_worker,
189 is_normal_assigned_only: board.is_normal_assigned_only,
190 is_comment_assigned_only: board.is_comment_assigned_only,
191 is_read_only: board.is_read_only,
192 is_read_assigned_only: board.is_read_assigned_only,
193 })
194 .collect(),
195 }
196}
197
198fn non_empty(value: &str) -> Result<String, String> {
199 if value.is_empty() {
200 Err("value must not be empty".to_owned())
201 } else {
202 Ok(value.to_owned())
203 }
204}
205
206fn user_workspace_node(node: crate::client::UserWorkspaceNode) -> UserWorkspaceNodeDetail {
207 UserWorkspaceNodeDetail {
208 id: node.id,
209 name: node.name,
210 icon: node.icon,
211 children: node
212 .children
213 .map(|nodes| nodes.into_iter().map(user_workspace_node).collect()),
214 }
215}
216
217fn user_dialog_options(
218 dialogs: std::collections::BTreeMap<String, crate::client::UserDialogOptions>,
219) -> std::collections::BTreeMap<String, UserDialogOptionsDetail> {
220 dialogs
221 .into_iter()
222 .map(|(board, options)| {
223 (
224 board,
225 UserDialogOptionsDetail {
226 board_id: options.board_id,
227 swimlane_id: options.swimlane_id,
228 list_id: options.list_id,
229 card_id: options.card_id,
230 },
231 )
232 })
233 .collect()
234}
235
236fn user_profile(profile: crate::client::UserProfile) -> UserProfileDetail {
237 UserProfileDetail {
238 chat_view_layout: profile.chat_view_layout,
239 email_to_card: profile.email_to_card,
240 notification_delivery: profile.notification_delivery,
241 my_dependencies: profile.my_dependencies,
242 notify_muted_activities: profile.notify_muted_activities,
243 notify_override_tray: profile.notify_override_tray,
244 notify_override_email: profile.notify_override_email,
245 webhook_hide_identity: profile.webhook_hide_identity,
246 unread_changes_since: profile.unread_changes_since,
247 keep_title_tags: profile.keep_title_tags,
248 checklist_ding_sound: profile.checklist_ding_sound,
249 dismissed_board_announcements: profile.dismissed_board_announcements,
250 card_label_text_override: profile.card_label_text_override,
251 show_label_text_override: profile.show_label_text_override,
252 board_labels: profile.board_labels,
253 hidden_boards: profile.hidden_boards,
254 starred_swimlanes: profile.starred_swimlanes,
255 starred_lists: profile.starred_lists,
256 starred_cards: profile.starred_cards,
257 default_board_template_id: profile.default_board_template_id,
258 default_board_template_board_id: profile.default_board_template_board_id,
259 board_views: profile.board_views,
260 show_board_dependencies: profile.show_board_dependencies,
261 show_my_dependencies: profile.show_my_dependencies,
262 board_visits: profile.board_visits,
263 dismissed_rule_suggestions: profile.dismissed_rule_suggestions,
264 date_format_override: profile.date_format_override,
265 full_name: profile.fullname,
266 avatar_url: profile.avatar_url,
267 email_buffer: profile.email_buffer,
268 show_desktop_drag_handles: profile.show_desktop_drag_handles,
269 left_menu_collapsed: profile.left_menu_collapsed,
270 collapsed_workspaces: profile.collapsed_workspaces,
271 left_menu_width: profile.left_menu_width,
272 submit_on_enter: profile.submit_on_enter,
273 open_many_cards_at_once: profile.open_many_cards_at_once,
274 all_boards_theme_tiles: profile.all_boards_theme_tiles,
275 global_theme_color: profile.global_theme_color,
276 global_theme_custom_colors: profile.global_theme_custom_colors,
277 ui_font: profile.ui_font,
278 ui_font_size: profile.ui_font_size,
279 ui_text_color: profile.ui_text_color,
280 ui_text_bg_color: profile.ui_text_bg_color,
281 dismissed_announcement_version: profile.dismissed_announcement_version,
282 card_maximized: profile.card_maximized,
283 card_collapsed: profile.card_collapsed,
284 show_activities: profile.show_activities,
285 custom_fields_grid: profile.custom_fields_grid,
286 trello_api_saved: profile.trello_api_saved,
287 hidden_minicard_label_text: profile.hidden_minicard_label_text,
288 initials: profile.initials,
289 board_workspaces_tree: profile
290 .board_workspaces_tree
291 .map(|nodes| nodes.into_iter().map(user_workspace_node).collect()),
292 board_workspace_assignments: profile.board_workspace_assignments,
293 invited_boards: profile.invited_boards,
294 language: profile.language,
295 map_provider: profile.map_provider,
296 move_and_copy_dialog: profile.move_and_copy_dialog.map(user_dialog_options),
297 move_checklist_dialog: profile.move_checklist_dialog.map(user_dialog_options),
298 copy_checklist_dialog: profile.copy_checklist_dialog.map(user_dialog_options),
299 notifications: profile.notifications.map(|items| {
300 items
301 .into_iter()
302 .map(|item| UserProfileNotificationDetail {
303 activity: item.activity,
304 read: item.read,
305 show_at: item.show_at,
306 })
307 .collect()
308 }),
309 rescue_card_description: profile.rescue_card_description,
310 show_cards_count_at: profile.show_cards_count_at,
311 start_day_of_week: profile.start_day_of_week,
312 starred_boards: profile.starred_boards,
313 starred_pages: profile.starred_pages.map(|items| {
314 items
315 .into_iter()
316 .map(|item| UserProfileStarredPageDetail {
317 url: item.url,
318 title: item.title,
319 })
320 .collect()
321 }),
322 default_board_id: profile.default_board_id,
323 icode: profile.icode,
324 board_view: profile.board_view,
325 list_sort_by: profile.list_sort_by,
326 all_boards_sort_by: profile.all_boards_sort_by,
327 templates_board_id: profile.templates_board_id,
328 card_templates_swimlane_id: profile.card_templates_swimlane_id,
329 list_templates_swimlane_id: profile.list_templates_swimlane_id,
330 board_templates_swimlane_id: profile.board_templates_swimlane_id,
331 sidebar_width: profile.sidebar_width,
332 list_widths: profile.list_widths,
333 list_constraints: profile.list_constraints,
334 auto_width_boards: profile.auto_width_boards,
335 board_sort_index: profile.board_sort_index,
336 fixed_list_width_boards: profile.fixed_list_width_boards,
337 fixed_list_widths: profile.fixed_list_widths,
338 swimlane_heights: profile.swimlane_heights,
339 collapsed_lists: profile.collapsed_lists,
340 collapsed_swimlanes: profile.collapsed_swimlanes,
341 collapsed_card_sections: profile.collapsed_card_sections,
342 keyboard_shortcuts: profile.keyboard_shortcuts,
343 vertical_scrollbars: profile.vertical_scrollbars,
344 show_week_of_year: profile.show_week_of_year,
345 date_format: profile.date_format,
346 mobile_mode: profile.mobile_mode,
347 card_zoom: profile.card_zoom,
348 calendar_system: profile.calendar_system,
349 collapsed_cards: profile.collapsed_cards,
350 card_last_views: profile.card_last_views,
351 }
352}