use crate::{
client::{WekanClientFactory, integrations::UpdateIntegrationRequest},
command_result::{
CommandSuccess, IntegrationSubmittedField as Field, IntegrationUpdateSuccess,
},
commands::{
authenticated::AuthenticatedContext, client_error::map_client_error_with_not_found,
},
credentials::{CredentialStore, SecretInputProvider},
error::AppError,
redaction::Redactor,
};
use clap::Args;
use secrecy::{ExposeSecret, SecretString};
#[derive(Debug, Args)]
#[command(
after_help = "Activity flags replace the complete subscription array. Empty updates send {}. Wekan writes fields sequentially and can retain earlier writes after a later failure."
)]
pub struct UpdateArgs {
#[command(flatten)]
target: super::Target,
#[arg(long)]
enabled: Option<bool>,
#[arg(long)]
title: Option<String>,
#[arg(long)]
url: Option<String>,
#[arg(long, conflicts_with = "clear_token")]
token_stdin: bool,
#[arg(long)]
clear_token: bool,
#[arg(long = "activity", conflicts_with = "clear_activities")]
activities: Vec<String>,
#[arg(long)]
clear_activities: bool,
#[arg(
long,
value_name = "JSON",
conflicts_with = "clear_notification_delivery"
)]
notification_delivery: Option<String>,
#[arg(long)]
clear_notification_delivery: bool,
}
pub(super) async fn execute(
args: UpdateArgs,
factory: &WekanClientFactory,
store: &dyn CredentialStore,
secrets: &dyn SecretInputProvider,
) -> Result<CommandSuccess, AppError> {
let context = AuthenticatedContext::load(factory, store)?;
let token = if args.token_stdin {
let raw = secrets.read_raw_secret()?;
let value = raw.expose_secret();
let value = value
.strip_suffix("\r\n")
.or_else(|| value.strip_suffix('\n'))
.unwrap_or(value);
if value.is_empty() {
return Err(AppError::invalid_input(
"token stdin must not be empty; use --clear-token to clear the token; no request was sent",
));
}
Some(SecretString::from(value.to_owned()))
} else if args.clear_token {
Some(SecretString::from(String::new()))
} else {
None
};
let request = UpdateIntegrationRequest {
enabled: args.enabled,
title: args.title,
url: args.url,
token,
activities: if args.clear_activities {
Some(Vec::new())
} else if args.activities.is_empty() {
None
} else {
Some(args.activities)
},
notification_delivery: if args.clear_notification_delivery {
Some(None)
} else {
args.notification_delivery.as_deref().map(|text| serde_json::from_str(text).map(Some)
.map_err(|_| AppError::invalid_input("notification delivery must be an object containing only typed webhook settings; no request was sent"))).transpose()?
},
};
let mut fields = Vec::new();
for (present, field) in [
(request.enabled.is_some(), Field::Enabled),
(request.title.is_some(), Field::Title),
(request.url.is_some(), Field::Url),
(request.token.is_some(), Field::Token),
(request.activities.is_some(), Field::Activities),
(
request.notification_delivery.is_some(),
Field::NotificationDelivery,
),
] {
if present {
fields.push(field);
}
}
let mut redactor = Redactor::with_secret(context.record().token());
if let Some(token) = &request.token {
redactor = redactor.and_secret(token);
}
let response = context
.client()
.update_integration(
&args.target.board_id,
&args.target.integration_id,
&request,
context.record().token(),
)
.await
.map_err(|e| {
map_client_error_with_not_found(
e,
&redactor,
"integration update",
"the Wekan board was not found",
)
})?;
Ok(CommandSuccess::IntegrationUpdated(
IntegrationUpdateSuccess {
board_id: args.target.board_id,
integration_id: response.integration_id,
submitted_fields: fields,
},
))
}