diff options
Diffstat (limited to 'src/bin/ngit/sub_commands/issue_create.rs')
| -rw-r--r-- | src/bin/ngit/sub_commands/issue_create.rs | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/src/bin/ngit/sub_commands/issue_create.rs b/src/bin/ngit/sub_commands/issue_create.rs new file mode 100644 index 0000000..0c4b677 --- /dev/null +++ b/src/bin/ngit/sub_commands/issue_create.rs | |||
| @@ -0,0 +1,122 @@ | |||
| 1 | use anyhow::{Context, Result, bail}; | ||
| 2 | use ngit::client::{Params, send_events, sign_event}; | ||
| 3 | use nostr::{EventBuilder, Tag, TagStandard, ToBech32, nips::nip19::Nip19Event}; | ||
| 4 | use nostr_sdk::Kind; | ||
| 5 | |||
| 6 | use crate::{ | ||
| 7 | client::{Client, Connect, fetching_with_report, get_repo_ref_from_cache}, | ||
| 8 | git::{Repo, RepoActions}, | ||
| 9 | login, | ||
| 10 | repo_ref::get_repo_coordinates_when_remote_unknown, | ||
| 11 | }; | ||
| 12 | |||
| 13 | pub async fn launch( | ||
| 14 | title: Option<String>, | ||
| 15 | body: Option<String>, | ||
| 16 | labels: Vec<String>, | ||
| 17 | ) -> Result<()> { | ||
| 18 | let git_repo = Repo::discover().context("failed to find a git repository")?; | ||
| 19 | let git_repo_path = git_repo.get_path()?; | ||
| 20 | |||
| 21 | let client = Client::new(Params::with_git_config_relay_defaults(&Some(&git_repo))); | ||
| 22 | let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?; | ||
| 23 | |||
| 24 | fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; | ||
| 25 | |||
| 26 | let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?; | ||
| 27 | |||
| 28 | // Resolve title — required | ||
| 29 | let title = match title { | ||
| 30 | Some(t) if !t.trim().is_empty() => t, | ||
| 31 | _ => bail!("--title is required to create an issue"), | ||
| 32 | }; | ||
| 33 | |||
| 34 | // Body defaults to empty string if not provided | ||
| 35 | let body = body.unwrap_or_default(); | ||
| 36 | |||
| 37 | // Login | ||
| 38 | let (signer, user_ref, _) = | ||
| 39 | login::login_or_signup(&Some(&git_repo), &None, &None, Some(&client), true).await?; | ||
| 40 | |||
| 41 | // Build NIP-34 GitIssue event (kind 1621) | ||
| 42 | // Tags: | ||
| 43 | // - `a` coordinate tags for each maintainer's repo announcement | ||
| 44 | // - `subject` — issue title | ||
| 45 | // - `t` — hashtag labels | ||
| 46 | // - `alt` — human-readable summary | ||
| 47 | let mut tags: Vec<Tag> = vec![]; | ||
| 48 | |||
| 49 | // Repo coordinate tags (one per maintainer) | ||
| 50 | for coord in repo_ref.coordinates() { | ||
| 51 | tags.push(Tag::from_standardized(TagStandard::Coordinate { | ||
| 52 | coordinate: coord.coordinate.clone(), | ||
| 53 | relay_url: coord.relays.first().cloned(), | ||
| 54 | uppercase: false, | ||
| 55 | })); | ||
| 56 | } | ||
| 57 | |||
| 58 | // Subject (title) | ||
| 59 | tags.push(Tag::parse(vec!["subject".to_string(), title.clone()])?); | ||
| 60 | |||
| 61 | // Hashtag labels | ||
| 62 | for label in &labels { | ||
| 63 | tags.push(Tag::hashtag(label)); | ||
| 64 | } | ||
| 65 | |||
| 66 | // Alt text | ||
| 67 | tags.push(Tag::custom( | ||
| 68 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("alt")), | ||
| 69 | vec![format!("git issue: {title}")], | ||
| 70 | )); | ||
| 71 | |||
| 72 | // Maintainer p-tags (so they get notified) | ||
| 73 | for pk in &repo_ref.maintainers { | ||
| 74 | tags.push(Tag::public_key(*pk)); | ||
| 75 | } | ||
| 76 | |||
| 77 | let issue_event = sign_event( | ||
| 78 | EventBuilder::new(Kind::GitIssue, body).tags(tags), | ||
| 79 | &signer, | ||
| 80 | "create issue".to_string(), | ||
| 81 | ) | ||
| 82 | .await?; | ||
| 83 | |||
| 84 | let event_id = issue_event.id; | ||
| 85 | |||
| 86 | let mut client = client; | ||
| 87 | client.set_signer(signer).await; | ||
| 88 | |||
| 89 | send_events( | ||
| 90 | &client, | ||
| 91 | Some(git_repo_path), | ||
| 92 | vec![issue_event], | ||
| 93 | user_ref.relays.write(), | ||
| 94 | repo_ref.relays.clone(), | ||
| 95 | true, | ||
| 96 | false, | ||
| 97 | ) | ||
| 98 | .await?; | ||
| 99 | |||
| 100 | let event_bech32 = if let Some(relay) = repo_ref.relays.first() { | ||
| 101 | Nip19Event { | ||
| 102 | event_id, | ||
| 103 | relays: vec![relay.clone()], | ||
| 104 | author: None, | ||
| 105 | kind: None, | ||
| 106 | } | ||
| 107 | .to_bech32()? | ||
| 108 | } else { | ||
| 109 | event_id.to_bech32()? | ||
| 110 | }; | ||
| 111 | |||
| 112 | println!("issue created: {event_id}"); | ||
| 113 | let dim = console::Style::new().color256(247); | ||
| 114 | println!( | ||
| 115 | "{}", | ||
| 116 | dim.apply_to(format!( | ||
| 117 | "view in gitworkshop.dev: https://gitworkshop.dev/{}", | ||
| 118 | &event_bech32, | ||
| 119 | )) | ||
| 120 | ); | ||
| 121 | Ok(()) | ||
| 122 | } | ||