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 user_id: user.user_id,
124 username: user.username,
125 full_name: user.profile.fullname.clone(),
126 profile: Box::new(user_profile(user.profile)),
127 created_through_api: user.created_through_api,
128 heartbeat: user.heartbeat,
129 session_data: user.session_data.map(|data| UserSessionData {
130 total_hits: data.total_hits,
131 }),
132 import_usernames: user.import_usernames,
133 emails: user
134 .emails
135 .into_iter()
136 .map(|email| UserEmail {
137 address: email.address,
138 verified: email.verified,
139 })
140 .collect(),
141 is_admin: user.is_admin,
142 login_disabled: user.login_disabled,
143 authentication_method: user.authentication_method,
144 created_at: user.created_at,
145 modified_at: user.modified_at,
146 last_connection_date: user.last_connection_date,
147 organizations: user
148 .orgs
149 .into_iter()
150 .map(|organization| UserOrganization {
151 organization_id: organization.org_id,
152 display_name: organization.org_display_name,
153 is_admin: organization.is_admin,
154 })
155 .collect(),
156 teams: user
157 .teams
158 .into_iter()
159 .map(|team| UserTeam {
160 team_id: team.team_id,
161 display_name: team.team_display_name,
162 })
163 .collect(),
164 boards: user
165 .boards
166 .into_iter()
167 .map(|board| UserBoardMembership {
168 board_id: board.board_id,
169 is_active: board.is_active,
170 is_admin: board.is_admin,
171 is_no_comments: board.is_no_comments,
172 is_comment_only: board.is_comment_only,
173 is_worker: board.is_worker,
174 is_normal_assigned_only: board.is_normal_assigned_only,
175 is_comment_assigned_only: board.is_comment_assigned_only,
176 is_read_only: board.is_read_only,
177 is_read_assigned_only: board.is_read_assigned_only,
178 })
179 .collect(),
180 }
181}
182
183fn non_empty(value: &str) -> Result<String, String> {
184 if value.is_empty() {
185 Err("value must not be empty".to_owned())
186 } else {
187 Ok(value.to_owned())
188 }
189}
190
191fn user_workspace_node(node: crate::client::UserWorkspaceNode) -> UserWorkspaceNodeDetail {
192 UserWorkspaceNodeDetail {
193 id: node.id,
194 name: node.name,
195 icon: node.icon,
196 children: node
197 .children
198 .map(|nodes| nodes.into_iter().map(user_workspace_node).collect()),
199 }
200}
201
202fn user_dialog_options(
203 dialogs: std::collections::BTreeMap<String, crate::client::UserDialogOptions>,
204) -> std::collections::BTreeMap<String, UserDialogOptionsDetail> {
205 dialogs
206 .into_iter()
207 .map(|(board, options)| {
208 (
209 board,
210 UserDialogOptionsDetail {
211 board_id: options.board_id,
212 swimlane_id: options.swimlane_id,
213 list_id: options.list_id,
214 card_id: options.card_id,
215 },
216 )
217 })
218 .collect()
219}
220
221fn user_profile(profile: crate::client::UserProfile) -> UserProfileDetail {
222 UserProfileDetail {
223 full_name: profile.fullname,
224 avatar_url: profile.avatar_url,
225 email_buffer: profile.email_buffer,
226 show_desktop_drag_handles: profile.show_desktop_drag_handles,
227 left_menu_collapsed: profile.left_menu_collapsed,
228 collapsed_workspaces: profile.collapsed_workspaces,
229 left_menu_width: profile.left_menu_width,
230 submit_on_enter: profile.submit_on_enter,
231 open_many_cards_at_once: profile.open_many_cards_at_once,
232 all_boards_theme_tiles: profile.all_boards_theme_tiles,
233 global_theme_color: profile.global_theme_color,
234 global_theme_custom_colors: profile.global_theme_custom_colors,
235 ui_font: profile.ui_font,
236 ui_font_size: profile.ui_font_size,
237 ui_text_color: profile.ui_text_color,
238 ui_text_bg_color: profile.ui_text_bg_color,
239 dismissed_announcement_version: profile.dismissed_announcement_version,
240 card_maximized: profile.card_maximized,
241 card_collapsed: profile.card_collapsed,
242 show_activities: profile.show_activities,
243 custom_fields_grid: profile.custom_fields_grid,
244 trello_api_saved: profile.trello_api_saved,
245 hidden_minicard_label_text: profile.hidden_minicard_label_text,
246 initials: profile.initials,
247 board_workspaces_tree: profile
248 .board_workspaces_tree
249 .map(|nodes| nodes.into_iter().map(user_workspace_node).collect()),
250 board_workspace_assignments: profile.board_workspace_assignments,
251 invited_boards: profile.invited_boards,
252 language: profile.language,
253 map_provider: profile.map_provider,
254 move_and_copy_dialog: profile.move_and_copy_dialog.map(user_dialog_options),
255 move_checklist_dialog: profile.move_checklist_dialog.map(user_dialog_options),
256 copy_checklist_dialog: profile.copy_checklist_dialog.map(user_dialog_options),
257 notifications: profile.notifications.map(|items| {
258 items
259 .into_iter()
260 .map(|item| UserProfileNotificationDetail {
261 activity: item.activity,
262 read: item.read,
263 })
264 .collect()
265 }),
266 rescue_card_description: profile.rescue_card_description,
267 show_cards_count_at: profile.show_cards_count_at,
268 start_day_of_week: profile.start_day_of_week,
269 starred_boards: profile.starred_boards,
270 starred_pages: profile.starred_pages.map(|items| {
271 items
272 .into_iter()
273 .map(|item| UserProfileStarredPageDetail {
274 url: item.url,
275 title: item.title,
276 })
277 .collect()
278 }),
279 default_board_id: profile.default_board_id,
280 icode: profile.icode,
281 board_view: profile.board_view,
282 list_sort_by: profile.list_sort_by,
283 all_boards_sort_by: profile.all_boards_sort_by,
284 templates_board_id: profile.templates_board_id,
285 card_templates_swimlane_id: profile.card_templates_swimlane_id,
286 list_templates_swimlane_id: profile.list_templates_swimlane_id,
287 board_templates_swimlane_id: profile.board_templates_swimlane_id,
288 sidebar_width: profile.sidebar_width,
289 list_widths: profile.list_widths,
290 list_constraints: profile.list_constraints,
291 auto_width_boards: profile.auto_width_boards,
292 board_sort_index: profile.board_sort_index,
293 fixed_list_width_boards: profile.fixed_list_width_boards,
294 fixed_list_widths: profile.fixed_list_widths,
295 swimlane_heights: profile.swimlane_heights,
296 collapsed_lists: profile.collapsed_lists,
297 collapsed_swimlanes: profile.collapsed_swimlanes,
298 collapsed_card_sections: profile.collapsed_card_sections,
299 keyboard_shortcuts: profile.keyboard_shortcuts,
300 vertical_scrollbars: profile.vertical_scrollbars,
301 show_week_of_year: profile.show_week_of_year,
302 date_format: profile.date_format,
303 mobile_mode: profile.mobile_mode,
304 card_zoom: profile.card_zoom,
305 }
306}