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 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, UserEmail, UserOrganization,
23        UserProfileDetail, UserProfileNotificationDetail, UserProfileStarredPageDetail,
24        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    /// Create or revoke user API tokens (site admin only).
42    Token(token::TokenArgs),
43    /// Show the currently authenticated Wekan user.
44    Current(current::CurrentArgs),
45
46    /// List cards related to the currently authenticated user.
47    Cards(cards::CardsArgs),
48
49    /// List Wekan users (site admin only).
50    List(list::ListArgs),
51
52    /// Show a Wekan user by ID or username (site admin only).
53    Get(get::GetArgs),
54
55    /// Submit Wekan's no-action update and return the unchanged user (site admin only).
56    Update(update::UpdateArgs),
57
58    /// Create a Wekan user (site admin only).
59    Create(create::CreateArgs),
60
61    /// List active boards visible for a user ID.
62    Boards(boards::BoardsArgs),
63
64    /// Transfer boards administered by a user to the current user.
65    TakeOwnership(take_ownership::TakeOwnershipArgs),
66
67    /// Disable a user's login and clear their sessions.
68    DisableLogin(disable_login::DisableLoginArgs),
69
70    /// Re-enable a user's login.
71    EnableLogin(enable_login::EnableLoginArgs),
72
73    /// Delete a Wekan user.
74    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_profile(profile: crate::client::UserProfile) -> UserProfileDetail {
203    UserProfileDetail {
204        full_name: profile.fullname,
205        avatar_url: profile.avatar_url,
206        email_buffer: profile.email_buffer,
207        show_desktop_drag_handles: profile.show_desktop_drag_handles,
208        left_menu_collapsed: profile.left_menu_collapsed,
209        collapsed_workspaces: profile.collapsed_workspaces,
210        left_menu_width: profile.left_menu_width,
211        submit_on_enter: profile.submit_on_enter,
212        open_many_cards_at_once: profile.open_many_cards_at_once,
213        all_boards_theme_tiles: profile.all_boards_theme_tiles,
214        global_theme_color: profile.global_theme_color,
215        global_theme_custom_colors: profile.global_theme_custom_colors,
216        ui_font: profile.ui_font,
217        ui_font_size: profile.ui_font_size,
218        ui_text_color: profile.ui_text_color,
219        ui_text_bg_color: profile.ui_text_bg_color,
220        dismissed_announcement_version: profile.dismissed_announcement_version,
221        card_maximized: profile.card_maximized,
222        card_collapsed: profile.card_collapsed,
223        show_activities: profile.show_activities,
224        custom_fields_grid: profile.custom_fields_grid,
225        trello_api_saved: profile.trello_api_saved,
226        hidden_minicard_label_text: profile.hidden_minicard_label_text,
227        initials: profile.initials,
228        board_workspaces_tree: profile
229            .board_workspaces_tree
230            .map(|nodes| nodes.into_iter().map(user_workspace_node).collect()),
231        board_workspace_assignments: profile.board_workspace_assignments,
232        invited_boards: profile.invited_boards,
233        language: profile.language,
234        map_provider: profile.map_provider,
235        move_and_copy_dialog: profile.move_and_copy_dialog,
236        move_checklist_dialog: profile.move_checklist_dialog,
237        copy_checklist_dialog: profile.copy_checklist_dialog,
238        notifications: profile.notifications.map(|items| {
239            items
240                .into_iter()
241                .map(|item| UserProfileNotificationDetail {
242                    activity: item.activity,
243                    read: item.read,
244                })
245                .collect()
246        }),
247        rescue_card_description: profile.rescue_card_description,
248        show_cards_count_at: profile.show_cards_count_at,
249        start_day_of_week: profile.start_day_of_week,
250        starred_boards: profile.starred_boards,
251        starred_pages: profile.starred_pages.map(|items| {
252            items
253                .into_iter()
254                .map(|item| UserProfileStarredPageDetail {
255                    url: item.url,
256                    title: item.title,
257                })
258                .collect()
259        }),
260        default_board_id: profile.default_board_id,
261        icode: profile.icode,
262        board_view: profile.board_view,
263        list_sort_by: profile.list_sort_by,
264        all_boards_sort_by: profile.all_boards_sort_by,
265        templates_board_id: profile.templates_board_id,
266        card_templates_swimlane_id: profile.card_templates_swimlane_id,
267        list_templates_swimlane_id: profile.list_templates_swimlane_id,
268        board_templates_swimlane_id: profile.board_templates_swimlane_id,
269        sidebar_width: profile.sidebar_width,
270        list_widths: profile.list_widths,
271        list_constraints: profile.list_constraints,
272        auto_width_boards: profile.auto_width_boards,
273        board_sort_index: profile.board_sort_index,
274        fixed_list_width_boards: profile.fixed_list_width_boards,
275        fixed_list_widths: profile.fixed_list_widths,
276        swimlane_heights: profile.swimlane_heights,
277        collapsed_lists: profile.collapsed_lists,
278        collapsed_swimlanes: profile.collapsed_swimlanes,
279        collapsed_card_sections: profile.collapsed_card_sections,
280        keyboard_shortcuts: profile.keyboard_shortcuts,
281        vertical_scrollbars: profile.vertical_scrollbars,
282        show_week_of_year: profile.show_week_of_year,
283        date_format: profile.date_format,
284        mobile_mode: profile.mobile_mode,
285        card_zoom: profile.card_zoom,
286    }
287}