reorganize signed_core into method-based APIs

This commit is contained in:
2026-10-03 12:03:29 +07:00
parent d00ee33eab
commit 023267001d
20 changed files with 833 additions and 697 deletions
+16 -16
View File
@@ -11,7 +11,7 @@ use nostr::nips::nip19::Nip19Coordinate;
use nostr_connect::prelude::*;
use nostr_sdk::client::SyncSummary;
use nostr_sdk::prelude::*;
use signed_core::{Announcement, RepoAddr, build_state, filters, identifier_from_name, repo_addr};
use signed_core::{Announcement, Filters, RepoAddr, RepoState, filters};
use signed_nostr::{SignedAuthUrlHandler, UniversalSigner, Update};
use crate::git_store::repo_mirror_path;
@@ -388,7 +388,7 @@ impl Backend {
return Task::ready(Err(anyhow!("Sign in to create a repository")));
};
let repo_id = identifier_from_name(&name);
let repo_id = RepoAddr::identifier_from_name(&name);
if repo_id.is_empty() || repo_id.len() > 100 {
return Task::ready(Err(anyhow!(
@@ -563,7 +563,7 @@ impl Backend {
return Task::ready(Err(anyhow!("Sign in to publish a repository")));
};
let repo_id = identifier_from_name(&name);
let repo_id = RepoAddr::identifier_from_name(&name);
if repo_id.is_empty() || repo_id.len() > 100 {
return Task::ready(Err(anyhow!(
@@ -694,8 +694,8 @@ impl Backend {
// Record the ngit-compatible `nostr.repo` marker,
// so the next scan detects the repository instead of offering to publish it again.
let coordinate = repo_addr(event.pubkey, repo_id.clone());
match Nip19Coordinate::new(coordinate, servers.clone()).to_bech32() {
let coordinate = RepoAddr::new(event.pubkey, repo_id.clone());
match Nip19Coordinate::new(coordinate.into(), servers.clone()).to_bech32() {
Ok(naddr) => {
let path = path.clone();
cx.background_spawn(async move {
@@ -876,7 +876,7 @@ impl Backend {
let Some(public_key) = self.current_user else {
return Task::ready(Err(anyhow!("Sign in to delete a repository")));
};
if public_key != addr.public_key {
if public_key != addr.public_key() {
return Task::ready(Err(anyhow!("Only the repository owner can delete it")));
}
@@ -888,9 +888,9 @@ impl Backend {
let db = client.database();
let mut events = Vec::new();
for filter in [
filters::announcement(&addr),
filters::state(&addr),
filters::activity(&addr),
addr.announcement_filter(),
addr.state_filter(),
addr.activity_filter(),
] {
events.extend(db.query(filter).await?);
}
@@ -914,7 +914,7 @@ impl Backend {
let result = async {
sync_bootstrap_only(
&client,
filters::grasp_list(public_key),
Filters::grasp_list(public_key),
SyncOptions::default(),
)
.await?;
@@ -961,8 +961,8 @@ impl Backend {
let me = self.current_user;
if let Some(me) = me {
self.subscribe_bootstrap(filters::notifications(me), cx);
self.subscribe_bootstrap(vec![filters::authored_activity(me)], cx);
self.subscribe_bootstrap(Filters::notifications(me), cx);
self.subscribe_bootstrap(vec![Filters::authored_activity(me)], cx);
let relays: HashSet<RelayUrl> = RepoListStore::global(cx)
.read(cx)
@@ -973,8 +973,8 @@ impl Backend {
if !relays.is_empty() {
let relays: Vec<RelayUrl> = relays.into_iter().collect();
self.connect_repo_relays(relays.clone(), filters::notifications(me), cx);
self.connect_repo_relays(relays, vec![filters::authored_activity(me)], cx);
self.connect_repo_relays(relays.clone(), Filters::notifications(me), cx);
self.connect_repo_relays(relays, vec![Filters::authored_activity(me)], cx);
}
}
@@ -1357,7 +1357,7 @@ pub async fn user_grasp_list_servers(
) -> Result<Vec<RelayUrl>, Error> {
let events: Vec<Event> = client
.database()
.query(filters::grasp_list(user))
.query(Filters::grasp_list(user))
.await?
.into_iter()
.collect();
@@ -1517,7 +1517,7 @@ async fn sign_state_event(
last_created_at + 1
};
let event = build_state(repo_id, refs, head)
let event = RepoState::build(repo_id, refs, head)
.custom_created_at(Timestamp::from_secs(created_at))
.finalize_async(signer)
.await
+5 -5
View File
@@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use anyhow::Error;
use gpui::{AppContext, Context, Task};
use nostr_sdk::prelude::*;
use signed_core::{Deletions, InboxItem, InboxReadState, filters, inbox};
use signed_core::{Deletions, Filters, InboxItem, InboxReadState, GitEvent, inbox};
use crate::backend::Backend;
@@ -97,7 +97,7 @@ pub async fn query_inbox(
me: PublicKey,
state: &InboxReadState,
) -> Result<(Vec<InboxItem>, usize), Error> {
let deletion_events = client.database().query(filters::deletions()).await?;
let deletion_events = client.database().query(Filters::deletions()).await?;
let deletions = Deletions::from_events(deletion_events);
let (notification_events, mut by_id) = fetch_notifications(client, me, &deletions).await?;
@@ -105,10 +105,10 @@ pub async fn query_inbox(
let mut activity = Vec::new();
for event in client
.database()
.query(filters::authored_activity(me))
.query(Filters::authored_activity(me))
.await?
{
if deletions.is_deleted(&event) || !filters::is_git_activity(&event) {
if deletions.is_deleted(&event) || !event.is_git_activity() {
continue;
}
by_id.entry(event.id).or_insert_with(|| event.clone());
@@ -169,7 +169,7 @@ async fn fetch_notifications(
let mut notifications: Vec<Event> = Vec::new();
let mut by_id: HashMap<EventId, Event> = HashMap::new();
for filter in filters::notifications(me) {
for filter in Filters::notifications(me) {
for event in client.database().query(filter).await? {
if deletions.is_deleted(&event) {
continue;
+2 -2
View File
@@ -4,7 +4,7 @@ use std::sync::Arc;
use anyhow::Error;
use gpui::{App, AppContext, Context, Entity, Global, SharedString, Task};
use signed_core::{Announcement, RepoAddr, repo_addr};
use signed_core::{Announcement, RepoAddr};
use signed_git::{LocalRepo, Nip34Binding, find_git_repos};
struct GlobalLocalReposStore(Entity<LocalReposStore>);
@@ -113,7 +113,7 @@ pub fn local_repo_addr(repo: &LocalRepo) -> Option<RepoAddr> {
let owner = binding.owner?;
let identifier = binding.identifier.as_deref()?;
Some(repo_addr(owner, identifier))
Some(RepoAddr::new(owner, identifier))
}
/// A scanned repository resolved against the known announcements.
+48 -41
View File
@@ -9,7 +9,8 @@ use nostr::event::IntoEventBuilder;
use nostr_sdk::prelude::*;
use settings::{EventFetchingStrategy, SettingsStore};
use signed_core::{
Announcement, Deletions, RepoAddr, RepoStatus, filters, parse_state, pull_request_patches,
Announcement, Deletions, Filters, GitEvent, PullRequest, RepoAddr, RepoState, RepoStatus,
filters,
};
use signed_git::Nip34Binding;
use signed_nostr::UniversalSigner;
@@ -210,8 +211,8 @@ impl RepoStore {
let deletion =
update.kind == Kind::EventDeletion || update.kind == Kind::RequestToVanish;
let coordinate = update.coordinate.as_ref() == Some(addr);
let author = update.author == addr.public_key;
let coordinate = update.coordinate.as_ref() == Some(addr.coordinate());
let author = update.author == addr.public_key();
let authored = (update.kind == Kind::GitRepoAnnouncement
|| update.kind == Kind::RepoState)
@@ -251,12 +252,12 @@ impl RepoStore {
let mut filters = vec![
Filter::new()
.kinds([Kind::GitRepoAnnouncement, Kind::RepoState])
.author(addr.public_key)
.identifier(addr.identifier.clone()),
filters::activity(addr),
.author(addr.public_key())
.identifier(addr.identifier()),
addr.activity_filter(),
];
// Deletion requests, NIP-09/62, must be known before any event is shown.
filters.extend(filters::deletions_for_repo(addr));
filters.extend(addr.deletion_filters());
filters
}
@@ -290,8 +291,8 @@ impl RepoStore {
let mut pubkeys = maintainers.to_vec();
// NIP-34 events tag the announcement author,
// which may not be a maintainer for subordinate forks.
if !pubkeys.contains(&addr.public_key) {
pubkeys.push(addr.public_key);
if !pubkeys.contains(&addr.public_key()) {
pubkeys.push(addr.public_key());
}
vec![
@@ -299,16 +300,16 @@ impl RepoStore {
Filter::new()
.kinds([Kind::GitRepoAnnouncement, Kind::RepoState])
.authors(pubkeys.clone())
.identifier(addr.identifier.clone()),
.identifier(addr.identifier()),
// Activity tagging a maintainer, resolved to their read relays.
Filter::new()
.kinds(filters::ACTIVITY_KINDS)
.coordinate(addr)
.coordinate(addr.coordinate())
.pubkeys(pubkeys.clone()),
// Activity authored by a maintainer, resolved to their write relays.
Filter::new()
.kinds(filters::ACTIVITY_KINDS)
.coordinate(addr)
.coordinate(addr.coordinate())
.authors(pubkeys.clone()),
// Deletions authored by a maintainer.
Filter::new()
@@ -377,10 +378,10 @@ impl RepoStore {
let work = cx.background_spawn(async move {
let (announcements, states, activity, deletion_events) = async {
let db = client.database();
let announcements = db.query(filters::announcement(&addr)).await?;
let states = db.query(filters::state(&addr)).await?;
let activity = db.query(filters::activity(&addr)).await?;
let deletion_events = db.query(filters::deletions()).await?;
let announcements = db.query(addr.announcement_filter()).await?;
let states = db.query(addr.state_filter()).await?;
let activity = db.query(addr.activity_filter()).await?;
let deletion_events = db.query(Filters::deletions()).await?;
Ok::<_, Error>((announcements, states, activity, deletion_events))
}
@@ -397,7 +398,7 @@ impl RepoStore {
.and_then(Announcement::from_event);
let all_states = states.into_iter().filter(|e| !deletions.is_deleted(e));
let state = utils::latest(all_states).map(|state| parse_state(&state));
let state = utils::latest(all_states).map(|state| RepoState::parse(&state));
let (mut issues, mut patches, mut pull_requests, mut statuses, mut comments) =
(Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new());
@@ -425,7 +426,7 @@ impl RepoStore {
.chain(&pull_requests)
.map(|e| e.id);
for filter in filters::comments_for(roots) {
for filter in Filters::comments_for(roots) {
for event in db.query(filter).await? {
if seen_comments.insert(event.id) {
comments.push(event);
@@ -443,7 +444,7 @@ impl RepoStore {
.map(|e| e.id);
for root in roots {
for event in db.query(filters::statuses_for([root])).await? {
for event in db.query(Filters::statuses_for([root])).await? {
if seen_statuses.insert(event.id) {
statuses.push(event);
}
@@ -516,7 +517,7 @@ impl RepoStore {
let head_changed = state
.as_ref()
.is_some_and(|(_, head)| this.head.as_deref() != head.as_deref());
.is_some_and(|state| this.head.as_deref() != state.head.as_deref());
let changed = first_pass
|| (!keep_hint && this.announcement != announcement)
@@ -549,8 +550,8 @@ impl RepoStore {
this.sync_maintainer_relays(&maintainers, cx);
if let Some((_, head)) = state {
this.head = head;
if let Some(state) = state {
this.head = state.head;
}
this.issues = issues;
@@ -579,8 +580,8 @@ impl RepoStore {
if !new_roots.is_empty() {
this.root_fetches.extend(new_roots.iter().copied());
let mut root_filters = filters::comments_for(new_roots.clone());
root_filters.push(filters::statuses_for(new_roots.iter().copied()));
let mut root_filters = Filters::comments_for(new_roots.clone());
root_filters.push(Filters::statuses_for(new_roots.iter().copied()));
let announced: Vec<RelayUrl> = this.repo_relays.iter().cloned().collect();
let backend = Backend::global(cx);
@@ -621,7 +622,7 @@ impl RepoStore {
pub fn is_author(&self, user: &PublicKey) -> bool {
self.addr
.as_ref()
.is_some_and(|addr| &addr.public_key == user)
.is_some_and(|addr| addr.public_key() == *user)
}
pub fn open_issue(&mut self, subject: Option<String>, content: String, cx: &mut Context<Self>) {
@@ -631,7 +632,7 @@ impl RepoStore {
};
let builder = GitIssue {
repository: addr,
repository: addr.into(),
content,
subject,
labels: Vec::new(),
@@ -644,7 +645,7 @@ impl RepoStore {
pub fn comments_of(&self, root: &EventId) -> impl Iterator<Item = &Event> {
self.comments
.iter()
.filter(move |e| signed_core::references_root(e, root))
.filter(move |e| e.references_root(root))
}
/// Comment on a root event, an issue or PR, per NIP-34, kind 1111.
@@ -745,9 +746,9 @@ impl RepoStore {
// The author's npub names their GRASP-06 namespace, `/prs/...`.
let author_npub = user.to_bech32().unwrap();
let owner = addr.public_key;
let owner = addr.public_key();
let euc = self.announcement.as_ref().and_then(|a| a.euc.clone());
let repo_id = addr.identifier.clone();
let repo_id = addr.identifier().to_owned();
let base_npub = owner.to_bech32().unwrap();
let push_relays = self
.announcement
@@ -857,7 +858,7 @@ impl RepoStore {
let clone = pr_clone_urls(prs_urls, base_clone);
let builder = GitPullRequest {
repository: addr.clone(),
repository: addr.clone().into(),
content: description,
subject,
labels: Vec::new(),
@@ -1069,7 +1070,8 @@ impl RepoStore {
// The first revision patch replies to the original root patch, NIP-34.
// Use the PR's `e` tag, or the oldest patch of the linked set if the PR has none.
let root_patch_id = root.tags.event_ids().next().or_else(|| {
pull_request_patches(root, self.patches.iter())
PullRequest::new(root)
.patches(self.patches.iter())
.first()
.map(|p| p.id)
});
@@ -1079,7 +1081,7 @@ impl RepoStore {
return;
};
let owner = addr.public_key;
let owner = addr.public_key();
let euc = self.announcement.as_ref().and_then(|a| a.euc.clone());
let root = root.clone();
@@ -1110,7 +1112,7 @@ impl RepoStore {
let builder = {
let builder = GitPullRequestUpdate {
repository: addr.clone(),
repository: addr.clone().into(),
pull_request_event: root.id,
pull_request_author: root.pubkey,
current_commit,
@@ -1182,9 +1184,9 @@ impl RepoStore {
let builder = EventBuilder::new(status.kind(), "").tags([
root_ref,
Tag::public_key(addr.public_key),
Tag::public_key(addr.public_key()),
Tag::public_key(root.pubkey),
Tag::coordinate(addr, None),
Tag::coordinate(addr.clone().into(), None),
]);
self.publish(builder, cx);
@@ -1479,7 +1481,7 @@ fn resolve_statuses(
.map(|root| {
let events = by_root.get(&root.id).map(Vec::as_slice).unwrap_or(&[]);
let status =
signed_core::resolve_status(events.iter().copied(), &root.pubkey, maintainers);
signed_core::RepoStatus::resolve(events.iter().copied(), &root.pubkey, maintainers);
(root.id, status)
})
.collect()
@@ -1518,7 +1520,10 @@ async fn publish_patch_series(
));
};
let mut tags = vec![Tag::coordinate(addr.clone(), None), Tag::public_key(owner)];
let mut tags = vec![
Tag::coordinate(addr.clone().into(), None),
Tag::public_key(owner),
];
if ix == 0 {
if let Ok(tag) = Tag::parse(["t", first_marker]) {
@@ -1586,11 +1591,13 @@ fn comment_builder(
CommentBuilder::new(content, parent_target)
.root(root_target)
.into_event_builder()
.tags([Tag::coordinate(addr.clone(), None)])
.tags([Tag::coordinate(addr.clone().into(), None)])
}
#[cfg(test)]
mod tests {
use signed_core::GitEvent;
use std::collections::HashSet;
use nostr_sdk::prelude::*;
@@ -1612,7 +1619,7 @@ mod tests {
let root = EventBuilder::new(Kind::GitIssue, "issue body")
.finalize(&keys)
.expect("signed event");
let addr = Coordinate::new(Kind::GitRepoAnnouncement, root.pubkey).identifier("my-repo");
let addr = signed_core::RepoAddr::new(root.pubkey, "my-repo");
let relay = RelayUrl::parse("wss://relay.example.com").expect("valid relay URL");
let event = comment_builder(&root, None, Some(&relay), &addr, "hi".into())
@@ -1639,14 +1646,14 @@ mod tests {
assert_eq!(e.as_slice()[1], root.id.to_hex());
// Signed's own `references_root` must keep matching the comment.
assert!(signed_core::references_root(&event, &root.id));
assert!(event.references_root(&root.id));
}
#[test]
fn maintainer_filters_name_owner_and_maintainers() {
let owner = Keys::generate().public_key();
let maintainer = Keys::generate().public_key();
let addr = Coordinate::new(Kind::GitRepoAnnouncement, owner).identifier("my-repo");
let addr = signed_core::RepoAddr::new(owner, "my-repo");
// The owner is not among the maintainers, as on a subordinate fork.
let filters = RepoStore::maintainer_filters(&addr, &[maintainer]);
+15 -13
View File
@@ -5,7 +5,7 @@ use std::time::Duration;
use anyhow::Error;
use gpui::{App, AppContext, Context, Entity, Global, Subscription};
use nostr_sdk::prelude::*;
use signed_core::{Announcement, Deletions, RepoAddr, filters, repo_addr};
use signed_core::{Announcement, Deletions, Filters, RepoAddr, filters};
use crate::backend::{Backend, BackendEvent};
use crate::refresh::{RefreshGate, RefreshRequest};
@@ -115,10 +115,10 @@ impl RepoListStore {
backend.update(cx, |backend, cx| {
backend.sync_bootstraps(
vec![
filters::all_announcements(),
filters::all_states(),
Filters::all_announcements(),
Filters::all_states(),
// Deletion requests, NIP-09/62, must be known before any announcement is shown.
filters::deletions(),
Filters::deletions(),
],
cx,
);
@@ -144,7 +144,7 @@ impl RepoListStore {
self.state_synced_repos.insert(addr.clone());
backend.update(cx, |backend, cx| {
backend.connect_repo_relays(relays, vec![filters::state(&addr)], cx);
backend.connect_repo_relays(relays, vec![addr.state_filter()], cx);
});
}
}
@@ -168,10 +168,10 @@ impl RepoListStore {
let client = backend.read(cx).client();
let work = cx.background_spawn(async move {
let filter = filters::all_announcements();
let filter = Filters::all_announcements();
let events = client.database().query(filter).await?;
let deletion_events = client.database().query(filters::deletions()).await?;
let deletion_events = client.database().query(Filters::deletions()).await?;
let deletions = Deletions::from_events(deletion_events);
// Dedup and sort off the main thread.
@@ -213,7 +213,7 @@ impl RepoListStore {
let Some(id) = event.tags.identifier() else {
continue;
};
let addr = repo_addr(event.pubkey, id);
let addr = RepoAddr::new(event.pubkey, id);
let Some(entry) = last_activity.get_mut(&addr) else {
continue;
};
@@ -230,10 +230,11 @@ impl RepoListStore {
if deletions.is_deleted(&event) {
continue;
}
for addr in event.tags.coordinates() {
if addr.kind != Kind::GitRepoAnnouncement {
for coordinate in event.tags.coordinates() {
if coordinate.kind != Kind::GitRepoAnnouncement {
continue;
}
let addr = RepoAddr::from(coordinate.clone());
let Some(entry) = last_activity.get_mut(&addr) else {
continue;
};
@@ -251,12 +252,13 @@ impl RepoListStore {
if deletions.is_deleted(&event) {
continue;
}
for addr in event.tags.coordinates() {
if addr.kind != Kind::GitRepoAnnouncement || !last_activity.contains_key(&addr)
for coordinate in event.tags.coordinates() {
if coordinate.kind != Kind::GitRepoAnnouncement
|| !last_activity.contains_key(&RepoAddr::from(coordinate.clone()))
{
continue;
}
let entry = counts.entry(addr).or_default();
let entry = counts.entry(RepoAddr::from(coordinate)).or_default();
match event.kind {
Kind::GitIssue => entry.issues += 1,
Kind::GitPullRequest => entry.pull_requests += 1,