use crate::auth::BlossomSigner;
use crate::protocol::BlobDescriptor;
pub const KIND_FILE_METADATA: u16 = 1063;
pub const KIND_SERVER_LIST: u16 = 10063;
pub fn build_file_metadata_event(
signer: &dyn BlossomSigner,
desc: &BlobDescriptor,
server_url: &str,
content_type: &str,
) -> serde_json::Value {
let blob_url = format!("{}/{}", server_url.trim_end_matches('/'), desc.sha256);
let tags = vec![
vec!["url".to_string(), blob_url],
vec!["m".to_string(), content_type.to_string()],
vec!["x".to_string(), desc.sha256.clone()],
vec!["ox".to_string(), desc.sha256.clone()],
vec!["size".to_string(), desc.size.to_string()],
];
let created_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
serde_json::json!({
"kind": KIND_FILE_METADATA,
"pubkey": signer.public_key_hex(),
"created_at": created_at,
"tags": tags,
"content": "",
})
}
pub fn build_server_list_event(
signer: &dyn BlossomSigner,
server_urls: &[String],
) -> serde_json::Value {
let tags: Vec<Vec<String>> = server_urls
.iter()
.map(|url| vec!["server".to_string(), url.clone()])
.collect();
let created_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
serde_json::json!({
"kind": KIND_SERVER_LIST,
"pubkey": signer.public_key_hex(),
"created_at": created_at,
"tags": tags,
"content": "",
})
}
pub async fn publish_to_relay(
relay_url: &str,
event_json: &serde_json::Value,
) -> Result<(), String> {
let http = reqwest::Client::new();
let resp = http
.post(relay_url)
.json(event_json)
.send()
.await
.map_err(|e| format!("publish to relay: {e}"))?;
if resp.status().is_success() {
tracing::info!(
relay = %relay_url,
kind = event_json["kind"].as_u64().unwrap_or(0),
"event published to relay"
);
Ok(())
} else {
let text = resp.text().await.unwrap_or_default();
Err(format!("relay rejected event: {text}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::auth::Signer;
#[test]
fn test_build_file_metadata_event() {
let signer = Signer::generate();
let desc = BlobDescriptor {
sha256: "abc123".repeat(10),
size: 1024,
content_type: Some("image/png".into()),
url: None,
uploaded: None,
};
let event = build_file_metadata_event(&signer, &desc, "https://example.com", "image/png");
assert_eq!(event["kind"], KIND_FILE_METADATA);
assert_eq!(event["tags"][0][0], "url");
assert_eq!(event["tags"][1][1], "image/png");
assert_eq!(event["tags"][2][0], "x");
assert_eq!(event["tags"][3][0], "ox");
assert_eq!(event["tags"][4][1], "1024");
}
#[test]
fn test_build_server_list_event() {
let signer = Signer::generate();
let servers = vec![
"https://blossom1.example.com".into(),
"https://blossom2.example.com".into(),
];
let event = build_server_list_event(&signer, &servers);
assert_eq!(event["kind"], KIND_SERVER_LIST);
let tags = event["tags"].as_array().unwrap();
assert_eq!(tags.len(), 2);
assert_eq!(tags[0][0], "server");
assert_eq!(tags[0][1], "https://blossom1.example.com");
}
}