upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/lib/client.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-09-04 11:32:05 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-09-04 14:23:54 +0100
commit771f944af447c202eba045936a36dee71ab797ac (patch)
treee691de4ebc8dde7ac4855e139881ff923bc254ce /src/lib/client.rs
parent949c6459aa7683453a7160423b689ceadb08954b (diff)
refactor: fix imports, etc based on restructure
move some functions out of ngit and into lib/mod and lib/git_events remove MockConnect from binaries so it is only used in the library. this was done: * mainly because automocks were not being imported from lib into each binary * but also because the these functions were being tested with MockConnect
Diffstat (limited to 'src/lib/client.rs')
-rw-r--r--src/lib/client.rs273
1 files changed, 266 insertions, 7 deletions
diff --git a/src/lib/client.rs b/src/lib/client.rs
index abde217..ace880b 100644
--- a/src/lib/client.rs
+++ b/src/lib/client.rs
@@ -21,8 +21,11 @@ use std::{
21use anyhow::{bail, Context, Result}; 21use anyhow::{bail, Context, Result};
22use async_trait::async_trait; 22use async_trait::async_trait;
23use console::Style; 23use console::Style;
24use futures::stream::{self, StreamExt}; 24use futures::{
25use indicatif::{MultiProgress, ProgressBar, ProgressState, ProgressStyle}; 25 future::join_all,
26 stream::{self, StreamExt},
27};
28use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle};
26#[cfg(test)] 29#[cfg(test)]
27use mockall::*; 30use mockall::*;
28use nostr::{nips::nip01::Coordinate, Event}; 31use nostr::{nips::nip01::Coordinate, Event};
@@ -34,14 +37,13 @@ use nostr_sdk::{
34use nostr_sqlite::SQLiteDatabase; 37use nostr_sqlite::SQLiteDatabase;
35 38
36use crate::{ 39use crate::{
37 config::get_dirs, 40 get_dirs,
41 git_events::{
42 event_is_cover_letter, event_is_patch_set_root, event_is_revision_root, status_kinds,
43 },
38 login::{get_logged_in_user, get_user_ref_from_cache}, 44 login::{get_logged_in_user, get_user_ref_from_cache},
39 repo_ref::RepoRef, 45 repo_ref::RepoRef,
40 repo_state::RepoState, 46 repo_state::RepoState,
41 sub_commands::{
42 list::status_kinds,
43 send::{event_is_patch_set_root, event_is_revision_root},
44 },
45}; 47};
46 48
47#[allow(clippy::struct_field_names)] 49#[allow(clippy::struct_field_names)]
@@ -1478,3 +1480,260 @@ pub async fn fetching_with_report(
1478 } 1480 }
1479 Ok(report) 1481 Ok(report)
1480} 1482}
1483
1484pub async fn get_proposals_and_revisions_from_cache(
1485 git_repo_path: &Path,
1486 repo_coordinates: HashSet<Coordinate>,
1487) -> Result<Vec<nostr::Event>> {
1488 let mut proposals = get_events_from_cache(
1489 git_repo_path,
1490 vec![
1491 nostr::Filter::default()
1492 .kind(nostr::Kind::GitPatch)
1493 .custom_tag(
1494 nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A),
1495 repo_coordinates
1496 .iter()
1497 .map(std::string::ToString::to_string)
1498 .collect::<Vec<String>>(),
1499 ),
1500 ],
1501 )
1502 .await?
1503 .iter()
1504 .filter(|e| event_is_patch_set_root(e))
1505 .cloned()
1506 .collect::<Vec<nostr::Event>>();
1507 proposals.sort_by_key(|e| e.created_at);
1508 proposals.reverse();
1509 Ok(proposals)
1510}
1511
1512pub async fn get_all_proposal_patch_events_from_cache(
1513 git_repo_path: &Path,
1514 repo_ref: &RepoRef,
1515 proposal_id: &nostr::EventId,
1516) -> Result<Vec<nostr::Event>> {
1517 let mut commit_events = get_events_from_cache(
1518 git_repo_path,
1519 vec![
1520 nostr::Filter::default()
1521 .kind(nostr::Kind::GitPatch)
1522 .event(*proposal_id),
1523 nostr::Filter::default()
1524 .kind(nostr::Kind::GitPatch)
1525 .id(*proposal_id),
1526 ],
1527 )
1528 .await?;
1529
1530 let permissioned_users: HashSet<PublicKey> = [
1531 repo_ref.maintainers.clone(),
1532 vec![
1533 commit_events
1534 .iter()
1535 .find(|e| e.id().eq(proposal_id))
1536 .context("proposal not in cache")?
1537 .author(),
1538 ],
1539 ]
1540 .concat()
1541 .iter()
1542 .copied()
1543 .collect();
1544 commit_events.retain(|e| permissioned_users.contains(&e.author()));
1545
1546 let revision_roots: HashSet<nostr::EventId> = commit_events
1547 .iter()
1548 .filter(|e| event_is_revision_root(e))
1549 .map(nostr::Event::id)
1550 .collect();
1551
1552 if !revision_roots.is_empty() {
1553 for event in get_events_from_cache(
1554 git_repo_path,
1555 vec![
1556 nostr::Filter::default()
1557 .kind(nostr::Kind::GitPatch)
1558 .events(revision_roots)
1559 .authors(permissioned_users.clone()),
1560 ],
1561 )
1562 .await?
1563 {
1564 commit_events.push(event);
1565 }
1566 }
1567
1568 Ok(commit_events
1569 .iter()
1570 .filter(|e| !event_is_cover_letter(e) && permissioned_users.contains(&e.author()))
1571 .cloned()
1572 .collect())
1573}
1574
1575#[allow(clippy::module_name_repetitions)]
1576#[allow(clippy::too_many_lines)]
1577pub async fn send_events(
1578 #[cfg(test)] client: &crate::client::MockConnect,
1579 #[cfg(not(test))] client: &Client,
1580 git_repo_path: &Path,
1581 events: Vec<nostr::Event>,
1582 my_write_relays: Vec<String>,
1583 repo_read_relays: Vec<String>,
1584 animate: bool,
1585 silent: bool,
1586) -> Result<()> {
1587 let fallback = [
1588 client.get_fallback_relays().clone(),
1589 if events
1590 .iter()
1591 .any(|e| e.kind().eq(&Kind::GitRepoAnnouncement))
1592 {
1593 client.get_blaster_relays().clone()
1594 } else {
1595 vec![]
1596 },
1597 ]
1598 .concat();
1599 let mut relays: Vec<&String> = vec![];
1600
1601 let all = &[
1602 repo_read_relays.clone(),
1603 my_write_relays.clone(),
1604 fallback.clone(),
1605 ]
1606 .concat();
1607 // add duplicates first
1608 for r in &repo_read_relays {
1609 let r_clean = remove_trailing_slash(r);
1610 if !my_write_relays
1611 .iter()
1612 .filter(|x| r_clean.eq(&remove_trailing_slash(x)))
1613 .count()
1614 > 1
1615 && !relays.iter().any(|x| r_clean.eq(&remove_trailing_slash(x)))
1616 {
1617 relays.push(r);
1618 }
1619 }
1620
1621 for r in all {
1622 let r_clean = remove_trailing_slash(r);
1623 if !relays.iter().any(|x| r_clean.eq(&remove_trailing_slash(x))) {
1624 relays.push(r);
1625 }
1626 }
1627
1628 let m = if silent {
1629 MultiProgress::with_draw_target(ProgressDrawTarget::hidden())
1630 } else {
1631 MultiProgress::new()
1632 };
1633 let pb_style = ProgressStyle::with_template(if animate {
1634 " {spinner} {prefix} {bar} {pos}/{len} {msg}"
1635 } else {
1636 " - {prefix} {bar} {pos}/{len} {msg}"
1637 })?
1638 .progress_chars("##-");
1639
1640 let pb_after_style =
1641 |symbol| ProgressStyle::with_template(format!(" {symbol} {}", "{prefix} {msg}",).as_str());
1642 let pb_after_style_succeeded = pb_after_style(if animate {
1643 console::style("✔".to_string())
1644 .for_stderr()
1645 .green()
1646 .to_string()
1647 } else {
1648 "y".to_string()
1649 })?;
1650
1651 let pb_after_style_failed = pb_after_style(if animate {
1652 console::style("✘".to_string())
1653 .for_stderr()
1654 .red()
1655 .to_string()
1656 } else {
1657 "x".to_string()
1658 })?;
1659
1660 #[allow(clippy::borrow_deref_ref)]
1661 join_all(relays.iter().map(|&relay| async {
1662 let relay_clean = remove_trailing_slash(&*relay);
1663 let details = format!(
1664 "{}{}{} {}",
1665 if my_write_relays
1666 .iter()
1667 .any(|r| relay_clean.eq(&remove_trailing_slash(r)))
1668 {
1669 " [my-relay]"
1670 } else {
1671 ""
1672 },
1673 if repo_read_relays
1674 .iter()
1675 .any(|r| relay_clean.eq(&remove_trailing_slash(r)))
1676 {
1677 " [repo-relay]"
1678 } else {
1679 ""
1680 },
1681 if fallback
1682 .iter()
1683 .any(|r| relay_clean.eq(&remove_trailing_slash(r)))
1684 {
1685 " [default]"
1686 } else {
1687 ""
1688 },
1689 relay_clean,
1690 );
1691 let pb = m.add(
1692 ProgressBar::new(events.len() as u64)
1693 .with_prefix(details.to_string())
1694 .with_style(pb_style.clone()),
1695 );
1696 if animate {
1697 pb.enable_steady_tick(Duration::from_millis(300));
1698 }
1699 pb.inc(0); // need to make pb display intially
1700 let mut failed = false;
1701 for event in &events {
1702 match client
1703 .send_event_to(git_repo_path, relay.as_str(), event.clone())
1704 .await
1705 {
1706 Ok(_) => pb.inc(1),
1707 Err(e) => {
1708 pb.set_style(pb_after_style_failed.clone());
1709 pb.finish_with_message(
1710 console::style(
1711 e.to_string()
1712 .replace("relay pool error:", "error:")
1713 .replace("event not published: ", "error: "),
1714 )
1715 .for_stderr()
1716 .red()
1717 .to_string(),
1718 );
1719 failed = true;
1720 break;
1721 }
1722 };
1723 }
1724 if !failed {
1725 pb.set_style(pb_after_style_succeeded.clone());
1726 pb.finish_with_message("");
1727 }
1728 }))
1729 .await;
1730 Ok(())
1731}
1732
1733fn remove_trailing_slash(s: &String) -> String {
1734 match s.as_str().strip_suffix('/') {
1735 Some(s) => s,
1736 None => s,
1737 }
1738 .to_string()
1739}