fix(nostr): remove live repo after announcement deletion

This commit is contained in:
DanConwayDev
2026-06-24 11:08:48 +01:00
parent 75517badaf
commit 5b5fbc2293
2 changed files with 116 additions and 0 deletions
+103
View File
@@ -12,6 +12,13 @@ use tar::Builder as TarBuilder;
use super::policy::{identifier_from_event, owner_directory_component, DeletionPolicy};
use crate::nostr::lifecycle::{DeletionSource, GitArchiveMetadata, HoldingMetadata};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct DeletedAnnouncementRepoScope {
owner: PublicKey,
owner_path_component: String,
identifier: String,
}
impl DeletionPolicy {
/// Move all currently served data authored by a targeted NIP-62 vanish
/// request through the same holding/archive deletion lifecycle used for
@@ -254,6 +261,7 @@ impl DeletionPolicy {
}
let mut deletable = Vec::with_capacity(matches.len());
let mut deleted_announcement_repo_scopes = HashSet::new();
for event in matches {
let mut event_metadata = metadata.clone();
@@ -303,6 +311,14 @@ impl DeletionPolicy {
);
continue;
}
if event_metadata.git_archive.is_some() {
deleted_announcement_repo_scopes.insert(DeletedAnnouncementRepoScope {
owner: event.pubkey,
owner_path_component: owner_dir,
identifier,
});
}
}
}
@@ -339,7 +355,94 @@ impl DeletionPolicy {
let delete_filter = Filter::new().ids(deletable);
if let Err(e) = self.ctx.database.delete(delete_filter).await {
tracing::warn!(error = %e, op = %context, "Failed to delete events from main DB");
return;
}
for scope in deleted_announcement_repo_scopes {
self.remove_live_repo_if_announcement_scope_unserved(scope, context)
.await;
}
}
async fn remove_live_repo_if_announcement_scope_unserved(
&self,
scope: DeletedAnnouncementRepoScope,
context: &str,
) {
let remaining_filter = Filter::new()
.kind(Kind::GitRepoAnnouncement)
.author(scope.owner)
.custom_tag(
SingleLetterTag::lowercase(Alphabet::D),
scope.identifier.clone(),
);
match self.ctx.database.query(remaining_filter).await {
Ok(remaining) if !remaining.is_empty() => {
tracing::debug!(
owner = %scope.owner.to_hex(),
identifier = %scope.identifier,
remaining = remaining.len(),
op = %context,
"Preserving live git repository because announcement scope is still served"
);
return;
}
Ok(_) => {}
Err(e) => {
tracing::warn!(
owner = %scope.owner.to_hex(),
identifier = %scope.identifier,
error = %e,
op = %context,
"Could not verify announcement scope deletion; preserving live git repository"
);
return;
}
}
let repo_path =
self.repo_path_for_owner_and_identifier(&scope.owner_path_component, &scope.identifier);
if !repo_path.exists() {
return;
}
if let Err(e) = std::fs::remove_dir_all(&repo_path) {
tracing::warn!(
owner = %scope.owner.to_hex(),
identifier = %scope.identifier,
repo = %repo_path.display(),
error = %e,
op = %context,
"Failed to remove live git repository after announcement deletion"
);
return;
}
if let Some(parent) = repo_path.parent() {
if let Err(e) = std::fs::remove_dir(parent) {
if e.kind() != std::io::ErrorKind::NotFound
&& e.kind() != std::io::ErrorKind::DirectoryNotEmpty
{
tracing::debug!(
owner = %scope.owner.to_hex(),
identifier = %scope.identifier,
parent = %parent.display(),
error = %e,
op = %context,
"Could not remove empty repository owner directory"
);
}
}
}
tracing::info!(
owner = %scope.owner.to_hex(),
identifier = %scope.identifier,
repo = %repo_path.display(),
op = %context,
"Removed live git repository after announcement deletion"
);
}
fn archive_output_path(
+13
View File
@@ -36,6 +36,15 @@ async fn announcement_deletion_creates_git_archive_and_metadata_linkage() {
let (announcement, repo_id) = publish_served_repo(&client, "git-archive-create").await;
let coordinate = announcement_coordinate(&announcement, &repo_id);
let owner_npub = client.public_key().to_bech32().expect("pubkey to npub");
let live_repo_path = relay
.git_data_path()
.join(owner_npub)
.join(format!("{repo_id}.git"));
assert!(
live_repo_path.is_dir(),
"live bare repo must exist before announcement deletion"
);
let deletion = build_deletion(&client, &[], std::slice::from_ref(&coordinate));
client.send_event(deletion).await.expect("send deletion");
@@ -62,6 +71,10 @@ async fn announcement_deletion_creates_git_archive_and_metadata_linkage() {
.join(".archive")
.join(&archive_rel_path);
assert!(archive_path.exists(), "archive file must exist on disk");
assert!(
!live_repo_path.exists(),
"live bare repo must be removed after successful announcement archival and deletion"
);
assert_eq!(owner, client.public_key().to_hex());
relay.stop().await;