mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-06 07:28:23 +00:00
Integrate git archival into deletion flow
- Archive git repositories before moving events to holding database - Extract npub and identifier from repository addresses (kind 30617) - Create tar.gz archives in .archive/<npub>/<identifier>-<timestamp>.tar.gz - Store archive metadata with deletion info and expiry timestamp - Pass archive path to holding database for cleanup coordination - Continue deletion even if archival fails (logged as error) - Export archive_repositories_for_addresses from policy module - Fix metadata file path in cleanup test (tar.gz.json not json)
This commit is contained in:
@@ -326,7 +326,7 @@ mod tests {
|
||||
|
||||
// Create test archive files
|
||||
let archive_path = archive_dir.join("test-repo-123456.tar.gz");
|
||||
let metadata_path = archive_dir.join("test-repo-123456.json");
|
||||
let metadata_path = archive_dir.join("test-repo-123456.tar.gz.json");
|
||||
|
||||
tokio::fs::write(&archive_path, b"fake archive data")
|
||||
.await
|
||||
|
||||
@@ -513,6 +513,44 @@ impl Nip34WritePolicy {
|
||||
return WritePolicyResult::Accept;
|
||||
};
|
||||
|
||||
// Archive git repositories before deletion (if any addresses are repositories)
|
||||
let archive_path = if !addresses.is_empty() {
|
||||
let git_data_path = std::path::Path::new(&self.ctx.config.git_data_path);
|
||||
let archive_base_path = git_data_path
|
||||
.parent()
|
||||
.unwrap_or(git_data_path)
|
||||
.join(".archive");
|
||||
|
||||
match crate::nostr::policy::archive_repositories_for_addresses(
|
||||
&addresses,
|
||||
git_data_path,
|
||||
&archive_base_path,
|
||||
&event.id,
|
||||
self.ctx.config.archive_retention_secs,
|
||||
) {
|
||||
Ok(path) => {
|
||||
if let Some(ref p) = path {
|
||||
tracing::info!(
|
||||
event_id = %event_id_str,
|
||||
archive_path = %p,
|
||||
"Successfully archived git repository"
|
||||
);
|
||||
}
|
||||
path
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
event_id = %event_id_str,
|
||||
error = %e,
|
||||
"Failed to archive git repository, continuing with deletion"
|
||||
);
|
||||
None // Continue with deletion even if archival fails
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Query for all dependent events
|
||||
match crate::nostr::policy::query_dependent_events(
|
||||
&self.ctx.database,
|
||||
@@ -539,6 +577,7 @@ impl Nip34WritePolicy {
|
||||
&all_events,
|
||||
&event.id,
|
||||
self.ctx.config.archive_retention_secs,
|
||||
archive_path,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
/// Handles querying for dependent events and moving events between databases
|
||||
/// for NIP-09 deletion request processing.
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
|
||||
use nostr_relay_builder::prelude::{Event, EventId, Filter, Kind};
|
||||
use nostr_relay_builder::prelude::{Event, EventId, Filter, Kind, PublicKey, ToBech32};
|
||||
|
||||
use super::SharedDatabase;
|
||||
use crate::database::{DeletionMetadata, HoldingDatabase};
|
||||
use crate::git::archive::{archive_repository, create_archive_metadata};
|
||||
|
||||
/// Query all events that depend on the given event IDs
|
||||
///
|
||||
@@ -197,6 +199,7 @@ fn has_event_tag(event: &Event, event_id: &EventId) -> bool {
|
||||
/// * `event_ids` - Event IDs to move
|
||||
/// * `deletion_event_id` - ID of the deletion request event
|
||||
/// * `retention_secs` - Retention period in seconds before permanent deletion
|
||||
/// * `archive_path` - Optional path to git archive (if repository was archived)
|
||||
///
|
||||
/// # Returns
|
||||
/// Number of events successfully moved
|
||||
@@ -206,6 +209,7 @@ pub async fn move_to_holding_database(
|
||||
event_ids: &HashSet<EventId>,
|
||||
deletion_event_id: &EventId,
|
||||
retention_secs: u64,
|
||||
archive_path: Option<String>,
|
||||
) -> Result<usize, String> {
|
||||
let mut moved_count = 0;
|
||||
let deletion_ts = std::time::SystemTime::now()
|
||||
@@ -238,7 +242,7 @@ pub async fn move_to_holding_database(
|
||||
deletion_timestamp: deletion_ts,
|
||||
deletion_event_id: *deletion_event_id,
|
||||
expiry_timestamp: deletion_ts + retention_secs,
|
||||
archive_path: None, // TODO: Add archive path when git archival is implemented
|
||||
archive_path: archive_path.clone(),
|
||||
};
|
||||
|
||||
// Store in holding database with deletion metadata
|
||||
@@ -262,6 +266,119 @@ pub async fn move_to_holding_database(
|
||||
Ok(moved_count)
|
||||
}
|
||||
|
||||
/// Archive git repositories for address deletions
|
||||
///
|
||||
/// When deleting repository announcements (kind 30617), this function archives
|
||||
/// the git repository data before deletion.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `addresses` - Repository addresses to archive (format: `30617:<pubkey>:<identifier>`)
|
||||
/// * `git_data_path` - Base path for git repositories
|
||||
/// * `archive_base_path` - Base path for archives (typically `.archive`)
|
||||
/// * `deletion_event_id` - ID of the deletion request event
|
||||
/// * `retention_secs` - Retention period in seconds
|
||||
///
|
||||
/// # Returns
|
||||
/// Optional path to the archive file (if any repositories were archived)
|
||||
pub fn archive_repositories_for_addresses(
|
||||
addresses: &[String],
|
||||
git_data_path: &Path,
|
||||
archive_base_path: &Path,
|
||||
deletion_event_id: &EventId,
|
||||
retention_secs: u64,
|
||||
) -> Result<Option<String>, String> {
|
||||
let deletion_ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
// Only archive kind 30617 repository announcements
|
||||
for address in addresses {
|
||||
let parts: Vec<&str> = address.split(':').collect();
|
||||
if parts.len() != 3 {
|
||||
continue; // Skip invalid addresses
|
||||
}
|
||||
|
||||
let kind = parts[0];
|
||||
if kind != "30617" {
|
||||
continue; // Only archive repository announcements
|
||||
}
|
||||
|
||||
let pubkey_hex = parts[1];
|
||||
let identifier = parts[2];
|
||||
|
||||
// Parse pubkey to get npub
|
||||
let pubkey = PublicKey::from_hex(pubkey_hex)
|
||||
.map_err(|e| format!("Invalid pubkey in address: {}", e))?;
|
||||
let npub = pubkey
|
||||
.to_bech32()
|
||||
.map_err(|e| format!("Failed to convert pubkey to npub: {}", e))?;
|
||||
|
||||
// Build repository path: <git_data_path>/<npub>/<identifier>.git
|
||||
let repo_path = git_data_path
|
||||
.join(&npub)
|
||||
.join(format!("{}.git", identifier));
|
||||
|
||||
if !repo_path.exists() {
|
||||
tracing::warn!(
|
||||
npub = %npub,
|
||||
identifier = %identifier,
|
||||
path = %repo_path.display(),
|
||||
"Repository directory not found, skipping archive"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Archive the repository
|
||||
match archive_repository(
|
||||
&repo_path,
|
||||
archive_base_path,
|
||||
&npub,
|
||||
identifier,
|
||||
deletion_ts,
|
||||
) {
|
||||
Ok(archive_path) => {
|
||||
tracing::info!(
|
||||
npub = %npub,
|
||||
identifier = %identifier,
|
||||
archive_path = %archive_path.display(),
|
||||
"Successfully archived git repository"
|
||||
);
|
||||
|
||||
// Create archive metadata
|
||||
if let Err(e) = create_archive_metadata(
|
||||
&archive_path,
|
||||
deletion_ts,
|
||||
deletion_event_id.to_hex(),
|
||||
npub.clone(),
|
||||
identifier.to_string(),
|
||||
retention_secs,
|
||||
) {
|
||||
tracing::error!(
|
||||
error = %e,
|
||||
archive_path = %archive_path.display(),
|
||||
"Failed to create archive metadata"
|
||||
);
|
||||
}
|
||||
|
||||
// Return the archive path (relative to working directory)
|
||||
return Ok(Some(archive_path.display().to_string()));
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
error = %e,
|
||||
npub = %npub,
|
||||
identifier = %identifier,
|
||||
"Failed to archive git repository"
|
||||
);
|
||||
return Err(format!("Failed to archive repository: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None) // No repositories archived
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -16,7 +16,9 @@ mod state;
|
||||
|
||||
pub use announcement::{AnnouncementPolicy, AnnouncementResult};
|
||||
pub use deletion::{DeletionPolicy, DeletionResult};
|
||||
pub use deletion_ops::{move_to_holding_database, query_dependent_events};
|
||||
pub use deletion_ops::{
|
||||
archive_repositories_for_addresses, move_to_holding_database, query_dependent_events,
|
||||
};
|
||||
pub use pr_event::PrEventPolicy;
|
||||
pub use related::{ReferenceResult, RelatedEventPolicy};
|
||||
pub use state::{StatePolicy, StateResult};
|
||||
|
||||
Reference in New Issue
Block a user