Skip to main content

wekan_cli/commands/
users.rs

1mod boards;
2mod cards;
3mod create;
4mod current;
5mod delete;
6mod disable_login;
7mod enable_login;
8mod get;
9mod list;
10mod take_ownership;
11mod update;
12
13#[cfg(test)]
14mod tests;
15
16use clap::{Args, Subcommand};
17
18use crate::{
19    client::{UserRecord, WekanClientFactory},
20    command_result::{
21        CommandSuccess, UserBoardMembership, UserDetail, UserEmail, UserOrganization,
22        UserProfileDetail, UserProfileNotificationDetail, UserProfileStarredPageDetail,
23        UserSessionData, UserTeam, UserWorkspaceNodeDetail,
24    },
25    credentials::{CredentialStore, SecretInputProvider},
26    error::AppError,
27    input::ConfirmationProvider,
28};
29
30use super::client_error::map_client_error;
31
32#[derive(Debug, Args)]
33pub struct UserArgs {
34    #[command(subcommand)]
35    pub command: UserCommand,
36}
37
38#[derive(Debug, Subcommand)]
39pub enum UserCommand {
40    /// Show the currently authenticated Wekan user.
41    Current(current::CurrentArgs),
42
43    /// List cards related to the currently authenticated user.
44    Cards(cards::CardsArgs),
45
46    /// List Wekan users (site admin only).
47    List(list::ListArgs),
48
49    /// Show a Wekan user by ID or username (site admin only).
50    Get(get::GetArgs),
51
52    /// Submit Wekan's no-action update and return the unchanged user (site admin only).
53    Update(update::UpdateArgs),
54
55    /// Create a Wekan user (site admin only).
56    Create(create::CreateArgs),
57
58    /// List active boards visible for a user ID.
59    Boards(boards::BoardsArgs),
60
61    /// Transfer boards administered by a user to the current user.
62    TakeOwnership(take_ownership::TakeOwnershipArgs),
63
64    /// Disable a user's login and clear their sessions.
65    DisableLogin(disable_login::DisableLoginArgs),
66
67    /// Re-enable a user's login.
68    EnableLogin(enable_login::EnableLoginArgs),
69
70    /// Delete a Wekan user.
71    Delete(delete::DeleteArgs),
72}
73
74pub(crate) async fn dispatch(
75    command: UserCommand,
76    client_factory: &WekanClientFactory,
77    credential_store: &dyn CredentialStore,
78    secret_input: &dyn SecretInputProvider,
79    confirmation: &dyn ConfirmationProvider,
80) -> Result<CommandSuccess, AppError> {
81    match command {
82        UserCommand::Current(args) => {
83            current::execute(args, client_factory, credential_store).await
84        }
85        UserCommand::Cards(args) => cards::execute(args, client_factory, credential_store).await,
86        UserCommand::List(args) => list::execute(args, client_factory, credential_store).await,
87        UserCommand::Get(args) => get::execute(args, client_factory, credential_store).await,
88        UserCommand::Update(args) => update::execute(args, client_factory, credential_store).await,
89        UserCommand::Create(args) => {
90            create::execute(args, client_factory, credential_store, secret_input).await
91        }
92        UserCommand::Boards(args) => boards::execute(args, client_factory, credential_store).await,
93        UserCommand::TakeOwnership(args) => {
94            take_ownership::execute(args, client_factory, credential_store, confirmation).await
95        }
96        UserCommand::DisableLogin(args) => {
97            disable_login::execute(args, client_factory, credential_store, confirmation).await
98        }
99        UserCommand::EnableLogin(args) => {
100            enable_login::execute(args, client_factory, credential_store).await
101        }
102        UserCommand::Delete(args) => {
103            delete::execute(args, client_factory, credential_store, confirmation).await
104        }
105    }
106}
107
108fn user_detail(user: UserRecord) -> UserDetail {
109    UserDetail {
110        user_id: user.user_id,
111        username: user.username,
112        full_name: user.profile.fullname.clone(),
113        profile: Box::new(user_profile(user.profile)),
114        created_through_api: user.created_through_api,
115        heartbeat: user.heartbeat,
116        session_data: user.session_data.map(|data| UserSessionData {
117            total_hits: data.total_hits,
118        }),
119        import_usernames: user.import_usernames,
120        emails: user
121            .emails
122            .into_iter()
123            .map(|email| UserEmail {
124                address: email.address,
125                verified: email.verified,
126            })
127            .collect(),
128        is_admin: user.is_admin,
129        login_disabled: user.login_disabled,
130        authentication_method: user.authentication_method,
131        created_at: user.created_at,
132        modified_at: user.modified_at,
133        last_connection_date: user.last_connection_date,
134        organizations: user
135            .orgs
136            .into_iter()
137            .map(|organization| UserOrganization {
138                organization_id: organization.org_id,
139                display_name: organization.org_display_name,
140                is_admin: organization.is_admin,
141            })
142            .collect(),
143        teams: user
144            .teams
145            .into_iter()
146            .map(|team| UserTeam {
147                team_id: team.team_id,
148                display_name: team.team_display_name,
149            })
150            .collect(),
151        boards: user
152            .boards
153            .into_iter()
154            .map(|board| UserBoardMembership {
155                board_id: board.board_id,
156                is_active: board.is_active,
157                is_admin: board.is_admin,
158                is_no_comments: board.is_no_comments,
159                is_comment_only: board.is_comment_only,
160                is_worker: board.is_worker,
161                is_normal_assigned_only: board.is_normal_assigned_only,
162                is_comment_assigned_only: board.is_comment_assigned_only,
163                is_read_only: board.is_read_only,
164                is_read_assigned_only: board.is_read_assigned_only,
165            })
166            .collect(),
167    }
168}
169
170fn non_empty(value: &str) -> Result<String, String> {
171    if value.is_empty() {
172        Err("value must not be empty".to_owned())
173    } else {
174        Ok(value.to_owned())
175    }
176}
177
178fn user_workspace_node(node: crate::client::UserWorkspaceNode) -> UserWorkspaceNodeDetail {
179    UserWorkspaceNodeDetail {
180        id: node.id,
181        name: node.name,
182        icon: node.icon,
183        children: node
184            .children
185            .map(|nodes| nodes.into_iter().map(user_workspace_node).collect()),
186    }
187}
188
189fn user_profile(profile: crate::client::UserProfile) -> UserProfileDetail {
190    UserProfileDetail {
191        full_name: profile.fullname,
192        avatar_url: profile.avatar_url,
193        email_buffer: profile.email_buffer,
194        show_desktop_drag_handles: profile.show_desktop_drag_handles,
195        left_menu_collapsed: profile.left_menu_collapsed,
196        collapsed_workspaces: profile.collapsed_workspaces,
197        left_menu_width: profile.left_menu_width,
198        submit_on_enter: profile.submit_on_enter,
199        open_many_cards_at_once: profile.open_many_cards_at_once,
200        all_boards_theme_tiles: profile.all_boards_theme_tiles,
201        global_theme_color: profile.global_theme_color,
202        global_theme_custom_colors: profile.global_theme_custom_colors,
203        ui_font: profile.ui_font,
204        ui_font_size: profile.ui_font_size,
205        ui_text_color: profile.ui_text_color,
206        ui_text_bg_color: profile.ui_text_bg_color,
207        dismissed_announcement_version: profile.dismissed_announcement_version,
208        card_maximized: profile.card_maximized,
209        card_collapsed: profile.card_collapsed,
210        show_activities: profile.show_activities,
211        custom_fields_grid: profile.custom_fields_grid,
212        trello_api_saved: profile.trello_api_saved,
213        hidden_minicard_label_text: profile.hidden_minicard_label_text,
214        initials: profile.initials,
215        board_workspaces_tree: profile
216            .board_workspaces_tree
217            .map(|nodes| nodes.into_iter().map(user_workspace_node).collect()),
218        board_workspace_assignments: profile.board_workspace_assignments,
219        invited_boards: profile.invited_boards,
220        language: profile.language,
221        map_provider: profile.map_provider,
222        move_and_copy_dialog: profile.move_and_copy_dialog,
223        move_checklist_dialog: profile.move_checklist_dialog,
224        copy_checklist_dialog: profile.copy_checklist_dialog,
225        notifications: profile.notifications.map(|items| {
226            items
227                .into_iter()
228                .map(|item| UserProfileNotificationDetail {
229                    activity: item.activity,
230                    read: item.read,
231                })
232                .collect()
233        }),
234        rescue_card_description: profile.rescue_card_description,
235        show_cards_count_at: profile.show_cards_count_at,
236        start_day_of_week: profile.start_day_of_week,
237        starred_boards: profile.starred_boards,
238        starred_pages: profile.starred_pages.map(|items| {
239            items
240                .into_iter()
241                .map(|item| UserProfileStarredPageDetail {
242                    url: item.url,
243                    title: item.title,
244                })
245                .collect()
246        }),
247        default_board_id: profile.default_board_id,
248        icode: profile.icode,
249        board_view: profile.board_view,
250        list_sort_by: profile.list_sort_by,
251        all_boards_sort_by: profile.all_boards_sort_by,
252        templates_board_id: profile.templates_board_id,
253        card_templates_swimlane_id: profile.card_templates_swimlane_id,
254        list_templates_swimlane_id: profile.list_templates_swimlane_id,
255        board_templates_swimlane_id: profile.board_templates_swimlane_id,
256        sidebar_width: profile.sidebar_width,
257        list_widths: profile.list_widths,
258        list_constraints: profile.list_constraints,
259        auto_width_boards: profile.auto_width_boards,
260        board_sort_index: profile.board_sort_index,
261        fixed_list_width_boards: profile.fixed_list_width_boards,
262        fixed_list_widths: profile.fixed_list_widths,
263        swimlane_heights: profile.swimlane_heights,
264        collapsed_lists: profile.collapsed_lists,
265        collapsed_swimlanes: profile.collapsed_swimlanes,
266        collapsed_card_sections: profile.collapsed_card_sections,
267        keyboard_shortcuts: profile.keyboard_shortcuts,
268        vertical_scrollbars: profile.vertical_scrollbars,
269        show_week_of_year: profile.show_week_of_year,
270        date_format: profile.date_format,
271        mobile_mode: profile.mobile_mode,
272        card_zoom: profile.card_zoom,
273    }
274}