upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/bin/ngit/sub_commands/issue_create.rs
blob: 454382394db4cba6481eef0d51e06a35930f86e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use anyhow::{Context, Result, bail};
use ngit::{
    client::{Params, send_events, sign_event},
    content_tags::{dedup_tags, tags_from_content},
};
use nostr::{EventBuilder, Tag, TagStandard, ToBech32, nips::nip19::Nip19Event};
use nostr_sdk::Kind;

use crate::{
    client::{Client, Connect, fetching_with_report, get_repo_ref_from_cache},
    git::{Repo, RepoActions},
    login,
    repo_ref::get_repo_coordinates_when_remote_unknown,
};

pub async fn launch(
    title: Option<String>,
    body: Option<String>,
    labels: Vec<String>,
) -> Result<()> {
    let git_repo = Repo::discover().context("failed to find a git repository")?;
    let git_repo_path = git_repo.get_path()?;

    let client = Client::new(Params::with_git_config_relay_defaults(&Some(&git_repo)));
    let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?;

    fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;

    let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?;

    // Resolve title — required
    let title = match title {
        Some(t) if !t.trim().is_empty() => t,
        _ => bail!("--title is required to create an issue"),
    };

    // Body defaults to empty string if not provided
    let body = body.unwrap_or_default();

    // Login
    let (signer, user_ref, _) =
        login::login_or_signup(&Some(&git_repo), &None, &None, Some(&client), true).await?;

    // Build NIP-34 GitIssue event (kind 1621)
    // Tags:
    //   - `a` coordinate tags for each maintainer's repo announcement
    //   - `subject` — issue title
    //   - `t` — hashtag labels
    //   - `alt` — human-readable summary
    let mut tags: Vec<Tag> = vec![];

    // Repo coordinate tags (one per maintainer)
    for coord in repo_ref.coordinates() {
        tags.push(Tag::from_standardized(TagStandard::Coordinate {
            coordinate: coord.coordinate.clone(),
            relay_url: coord.relays.first().cloned(),
            uppercase: false,
        }));
    }

    // Subject (title)
    tags.push(Tag::parse(vec!["subject".to_string(), title.clone()])?);

    // Hashtag labels
    for label in &labels {
        tags.push(Tag::hashtag(label));
    }

    // Alt text
    tags.push(Tag::custom(
        nostr::TagKind::Custom(std::borrow::Cow::Borrowed("alt")),
        vec![format!("git issue: {title}")],
    ));

    // Maintainer p-tags (so they get notified)
    for pk in &repo_ref.maintainers {
        tags.push(Tag::public_key(*pk));
    }

    // NIP-21 mention tags: q tags for cited events/addresses, p tags for cited
    // pubkeys
    tags.extend(tags_from_content(&body, Some(git_repo_path)).await?);
    let tags = dedup_tags(tags);

    let issue_event = sign_event(
        EventBuilder::new(Kind::GitIssue, body).tags(tags),
        &signer,
        "create issue".to_string(),
    )
    .await?;

    let event_id = issue_event.id;

    let mut client = client;
    client.set_signer(signer).await;

    send_events(
        &client,
        Some(git_repo_path),
        vec![issue_event],
        user_ref.relays.write(),
        repo_ref.relays.clone(),
        true,
        false,
    )
    .await?;

    let event_bech32 = if let Some(relay) = repo_ref.relays.first() {
        Nip19Event {
            event_id,
            relays: vec![relay.clone()],
            author: None,
            kind: None,
        }
        .to_bech32()?
    } else {
        event_id.to_bech32()?
    };

    println!("issue created: {event_id}");
    let dim = console::Style::new().color256(247);
    println!(
        "{}",
        dim.apply_to(format!(
            "view in gitworkshop.dev: https://gitworkshop.dev/{}",
            &event_bech32,
        ))
    );
    Ok(())
}