wekan_cli/commands/profile/
remove.rs1use clap::Args;
2
3use crate::{
4 command_result::{
5 CancellationSuccess, CommandSuccess, DestructiveOperation, ProfileRemoveSuccess,
6 },
7 config::profiles::{ProfileStore, parse_profile_name},
8 credentials::CredentialStore,
9 error::{AppError, ErrorCode},
10 input::{
11 ConfirmationArgs, ConfirmationDecision, ConfirmationProvider, ConfirmationRequest,
12 confirm_or_skip,
13 },
14};
15
16use super::{ensure_logged_out, profile_error, profile_not_found};
17
18#[derive(Debug, Args)]
19pub struct RemoveArgs {
20 #[arg(value_parser = parse_profile_name)]
21 pub name: String,
22 #[arg(long)]
24 pub force: bool,
25
26 #[command(flatten)]
27 pub confirmation: ConfirmationArgs,
28}
29
30struct RemovalPreview {
31 revision: u64,
32 server: String,
33 active: bool,
34}
35
36pub(super) fn execute(
37 args: RemoveArgs,
38 profile_store: &dyn ProfileStore,
39 credential_store: &dyn CredentialStore,
40 confirmation: &dyn ConfirmationProvider,
41) -> Result<CommandSuccess, AppError> {
42 let preview = preview_removal(&args, profile_store, credential_store)?;
43 let prompt = if preview.active {
44 format!(
45 "Remove active profile `{}` for {} and clear the active selection?",
46 args.name, preview.server
47 )
48 } else {
49 format!("Remove profile `{}` for {}?", args.name, preview.server)
50 };
51 let request = ConfirmationRequest::new(DestructiveOperation::ProfileRemove, prompt);
52 match confirm_or_skip(&args.confirmation, confirmation, &request)? {
53 ConfirmationDecision::Proceed => {}
54 ConfirmationDecision::Cancelled => {
55 return Ok(CommandSuccess::Cancelled(CancellationSuccess::new(
56 DestructiveOperation::ProfileRemove,
57 )));
58 }
59 }
60
61 let mut mutation = profile_store.lock_mutation().map_err(AppError::from)?;
62 let credential_namespace = profile_store
63 .credential_namespace()
64 .map_err(AppError::from)?;
65 let profile = mutation
66 .document()
67 .profile(&args.name)
68 .ok_or_else(|| profile_not_found(&args.name))?;
69 let server = profile.server().to_owned();
70 let active = mutation.document().active_profile() == Some(args.name.as_str());
71 if preview.revision != mutation.document().revision()
72 || preview.server != server
73 || preview.active != active
74 {
75 return Err(AppError::configuration(format!(
76 "profile `{}` changed while awaiting confirmation; retry the removal",
77 args.name
78 )));
79 }
80 if active && !args.force {
81 return Err(profile_error(
82 ErrorCode::ProfileInUse,
83 &args.name,
84 format!(
85 "profile `{}` is active; switch profiles first or pass --force to remove it and clear the active selection",
86 args.name
87 ),
88 ));
89 }
90
91 ensure_logged_out(credential_store, &args.name, &server, &credential_namespace)?;
92 let mut document = mutation.document().clone();
93 document.remove(&args.name);
94 if active {
95 document.set_active_profile(None);
96 }
97 let active_profile = document.active_profile().map(str::to_owned);
98 mutation.save(document)?;
99 Ok(CommandSuccess::ProfileRemoved(ProfileRemoveSuccess {
100 name: args.name,
101 server,
102 removed: true,
103 active_profile,
104 }))
105}
106
107fn preview_removal(
108 args: &RemoveArgs,
109 profile_store: &dyn ProfileStore,
110 credential_store: &dyn CredentialStore,
111) -> Result<RemovalPreview, AppError> {
112 let snapshot = profile_store.read().map_err(AppError::from)?;
113 let credential_namespace = profile_store
114 .credential_namespace()
115 .map_err(AppError::from)?;
116 let profile = snapshot
117 .document()
118 .profile(&args.name)
119 .ok_or_else(|| profile_not_found(&args.name))?;
120 let server = profile.server().to_owned();
121 let active = snapshot.document().active_profile() == Some(args.name.as_str());
122 if active && !args.force {
123 return Err(profile_error(
124 ErrorCode::ProfileInUse,
125 &args.name,
126 format!(
127 "profile `{}` is active; switch profiles first or pass --force to remove it and clear the active selection",
128 args.name
129 ),
130 ));
131 }
132 ensure_logged_out(credential_store, &args.name, &server, &credential_namespace)?;
133
134 Ok(RemovalPreview {
135 revision: snapshot.document().revision(),
136 server,
137 active,
138 })
139}