Skip to main content

wekan_cli/commands/
attachments.rs

1mod delete;
2mod download;
3mod export;
4mod get;
5mod list;
6#[cfg(test)]
7mod tests;
8mod transfer;
9mod upload;
10
11use crate::{
12    client::WekanClientFactory, command_result::CommandSuccess, credentials::CredentialStore,
13    error::AppError, input::ConfirmationProvider,
14};
15use clap::{Args, Subcommand};
16
17#[derive(Debug, Args)]
18pub struct AttachmentArgs {
19    #[command(subcommand)]
20    pub command: AttachmentCommand,
21}
22
23#[derive(Debug, Subcommand)]
24pub enum AttachmentCommand {
25    /// Export a native artifact to a file or validated raw stdout.
26    Export(export::ExportArgs),
27    /// List attachments. Wekan v11.06 ignores deeper selectors and returns all board attachments.
28    List(list::ListArgs),
29    /// Read attachment metadata and versions.
30    Get(get::GetArgs),
31    /// Upload a file or stdin (-). Pinned Wekan JSON uploads return HTTP 408.
32    Upload(upload::UploadArgs),
33    /// Download an attachment to a file or raw stdout (-).
34    Download(download::DownloadArgs),
35    /// Copy an attachment to a card. Pinned Wekan JSON copies return HTTP 408.
36    Copy(transfer::TransferArgs),
37    /// Move an attachment to a card. Pinned Wekan JSON moves return HTTP 408.
38    Move(transfer::TransferArgs),
39    /// Permanently delete an attachment; requires confirmation or --yes.
40    Delete(delete::DeleteArgs),
41    /// Upload and select a board background. Pinned Wekan JSON uploads return HTTP 408.
42    UploadBackground(upload::BackgroundArgs),
43    /// Download the current board background to a file or raw stdout (-).
44    DownloadBackground(download::BackgroundArgs),
45}
46
47impl AttachmentCommand {
48    pub(crate) fn validate_input(&self) -> Result<(), AppError> {
49        match self {
50            Self::Upload(a) => {
51                a.source.name()?;
52            }
53            Self::UploadBackground(a) => {
54                a.source.name()?;
55            }
56            _ => {}
57        }
58        Ok(())
59    }
60    pub(crate) fn download_destination(&self) -> Option<&download::DestinationArgs> {
61        match self {
62            Self::Download(a) => Some(&a.destination),
63            Self::DownloadBackground(a) => Some(&a.destination),
64            _ => None,
65        }
66    }
67}
68
69pub(crate) async fn dispatch(
70    command: AttachmentCommand,
71    factory: &WekanClientFactory,
72    store: &dyn CredentialStore,
73    confirmation: &dyn ConfirmationProvider,
74) -> Result<CommandSuccess, AppError> {
75    match command {
76        AttachmentCommand::Export(args) => export::execute(args, factory, store).await,
77        AttachmentCommand::List(a) => list::execute(a, factory, store).await,
78        AttachmentCommand::Get(a) => get::execute(a, factory, store).await,
79        AttachmentCommand::Upload(a) => upload::execute(a, factory, store).await,
80        AttachmentCommand::UploadBackground(a) => upload::background(a, factory, store).await,
81        AttachmentCommand::Download(a) => {
82            download::execute(Some(a.attachment_id), None, a.destination, factory, store).await
83        }
84        AttachmentCommand::DownloadBackground(a) => {
85            download::execute(None, Some(a.board_id), a.destination, factory, store).await
86        }
87        AttachmentCommand::Copy(a) => transfer::execute(a, false, factory, store).await,
88        AttachmentCommand::Move(a) => transfer::execute(a, true, factory, store).await,
89        AttachmentCommand::Delete(a) => delete::execute(a, factory, store, confirmation).await,
90    }
91}
92
93fn identifier(value: &str) -> Result<String, String> {
94    let value = crate::client::trim_javascript_whitespace(value);
95    if value.is_empty() || matches!(value, "." | "..") {
96        Err("ID must be nonblank and cannot be . or ..".to_owned())
97    } else {
98        Ok(value.to_owned())
99    }
100}
101
102fn file_error(operation: &str, path: &std::path::Path, error: std::io::Error) -> AppError {
103    AppError::new(
104        crate::error::ErrorCode::FileIoError,
105        format!(
106            "could not {operation} attachment file `{}`: {}",
107            crate::input::escape_terminal_text(&path.display().to_string()),
108            crate::input::escape_terminal_text(&error.to_string())
109        ),
110        crate::exit_code::StableExitCode::Transport,
111    )
112}