//! `attach-issue ` and `attach-comment //! ` — upload a file as an attachment. Prints the browser //! download URL. use std::path::{Path, PathBuf}; use anyhow::{Context, Result, bail}; use clap::Args as ClapArgs; use forgejo_api::structs::{ Attachment, IssueCreateIssueAttachmentQuery, IssueCreateIssueCommentAttachmentQuery, }; use crate::client::{Client, index}; #[derive(ClapArgs)] pub struct IssueArgs { /// Issue number. number: u64, /// File path to upload. file: PathBuf, } #[derive(ClapArgs)] pub struct CommentArgs { /// Comment id. id: u64, /// File path to upload. file: PathBuf, } /// # Errors /// /// Returns an error if the input file doesn't exist or can't be /// read. Propagates any transport error from the Forgejo REST call /// (network unreachable, 4xx/5xx response, token missing/invalid) /// and any I/O error from writing the browser download URL to stdout. pub fn run_issue(client: &Client, args: IssueArgs) -> Result<()> { if !args.file.is_file() { bail!( "hive-forge attach-issue: file not found: {}", args.file.display() ); } let (owner, name) = client.owner_repo()?; let bytes = read_file(&args.file)?; let query = IssueCreateIssueAttachmentQuery { name: file_name(&args.file), updated_at: None, }; let resp = client .api() .issue_create_issue_attachment(owner, name, index(args.number)?, &bytes, query) .send()?; print_url(&resp); Ok(()) } /// # Errors /// /// Returns an error if the input file doesn't exist or can't be /// read. Propagates any transport error from the Forgejo REST call /// (network unreachable, 4xx/5xx response, token missing/invalid) /// and any I/O error from writing the browser download URL to stdout. pub fn run_comment(client: &Client, args: CommentArgs) -> Result<()> { if !args.file.is_file() { bail!( "hive-forge attach-comment: file not found: {}", args.file.display() ); } let (owner, name) = client.owner_repo()?; let bytes = read_file(&args.file)?; let query = IssueCreateIssueCommentAttachmentQuery { name: file_name(&args.file), updated_at: None, }; let resp = client .api() .issue_create_issue_comment_attachment(owner, name, index(args.id)?, &bytes, query) .send()?; print_url(&resp); Ok(()) } fn read_file(path: &Path) -> Result> { std::fs::read(path).with_context(|| format!("read {}", path.display())) } /// The upload's attachment name — the file's basename, matching what /// the old multipart form (which sent the file with its real name) /// made the server record. fn file_name(path: &Path) -> Option { path.file_name().map(|n| n.to_string_lossy().into_owned()) } fn print_url(v: &Attachment) { if let Some(url) = &v.browser_download_url { println!("{url}"); } }