lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 3f659008241fb347dad36de3e98b90b6ff56caf6
parent e211b80133a563ab5838a5a0675b612a7c92ff41
Author: triesap <tyson@radroots.org>
Date:   Thu, 11 Jun 2026 16:57:12 -0700

events: add farm file and auth models

- add NIP-94-compatible Field file metadata models separate from message files
- add NIP-42 relay auth and NIP-98 HTTP auth event models
- cover file and auth serde shapes with unit tests

Diffstat:
Acrates/events/src/farm_file.rs | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events/src/http_auth.rs | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/events/src/lib.rs | 2++
Acrates/events/src/relay_auth.rs | 40++++++++++++++++++++++++++++++++++++++++
4 files changed, 221 insertions(+), 0 deletions(-)

diff --git a/crates/events/src/farm_file.rs b/crates/events/src/farm_file.rs @@ -0,0 +1,123 @@ +#![forbid(unsafe_code)] + +use crate::farm_crdt::RadrootsFarmCrdtDocumentKind; +use crate::farm_workspace::RadrootsFarmWorkspaceRef; +use crate::kinds::KIND_FARM_FILE_METADATA as KIND_FARM_FILE_METADATA_EVENT; + +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; + +pub const KIND_FARM_FILE_METADATA: u32 = KIND_FARM_FILE_METADATA_EVENT; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsFarmFileMetadata { + pub d_tag: String, + pub workspace: RadrootsFarmWorkspaceRef, + pub farm_group_id: String, + pub owner_document_id: String, + pub owner_document_kind: RadrootsFarmCrdtDocumentKind, + pub caption: Option<String>, + pub url: String, + pub mime_type: String, + pub sha256: String, + pub original_sha256: Option<String>, + pub size_bytes: Option<u64>, + pub dimensions: Option<RadrootsFarmFileDimensions>, + pub blurhash: Option<String>, + pub thumb: Option<RadrootsFarmFileSource>, + pub image: Option<RadrootsFarmFileSource>, + pub alt: Option<String>, + pub fallbacks: Vec<String>, +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RadrootsFarmFileDimensions { + pub w: u32, + pub h: u32, +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsFarmFileSource { + pub url: String, + pub mime_type: Option<String>, + pub dimensions: Option<RadrootsFarmFileDimensions>, +} + +#[cfg(all(test, feature = "serde"))] +mod tests { + use super::*; + + #[test] + fn file_metadata_kind_uses_nip94_file_metadata_kind() { + assert_eq!(KIND_FARM_FILE_METADATA, 1063); + } + + #[test] + fn file_metadata_remains_separate_from_message_file_model() { + let metadata = sample_file_metadata(); + + assert_eq!(metadata.d_tag, "EFGHIJKLMNOPQRSTUVWXYZ"); + assert_eq!(metadata.owner_document_id, "DEFGHIJKLMNOPQRSTUVWXY"); + assert_eq!( + metadata.owner_document_kind, + RadrootsFarmCrdtDocumentKind::FarmTask + ); + assert_eq!(metadata.caption.as_deref(), Some("Tomatoes harvested from Patch Y.")); + assert_eq!(metadata.mime_type, "image/jpeg"); + assert_eq!(metadata.dimensions, Some(RadrootsFarmFileDimensions { w: 1600, h: 1200 })); + assert_eq!(metadata.fallbacks.len(), 1); + } + + #[test] + fn file_metadata_serializes_stable_content_shape() { + let value = serde_json::to_value(sample_file_metadata()).unwrap(); + + assert_eq!(value["workspace"]["d_tag"], "ABCDEFGHIJKLMNOPQRSTUV"); + assert_eq!(value["farm_group_id"], "BCDEFGHIJKLMNOPQRSTUVW"); + assert_eq!(value["owner_document_kind"], "FarmTask"); + assert_eq!(value["caption"], "Tomatoes harvested from Patch Y."); + assert_eq!(value["mime_type"], "image/jpeg"); + assert_eq!( + value["sha256"], + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + ); + assert_eq!(value["dimensions"]["w"], 1600); + assert_eq!(value["dimensions"]["h"], 1200); + } + + fn sample_file_metadata() -> RadrootsFarmFileMetadata { + RadrootsFarmFileMetadata { + d_tag: "EFGHIJKLMNOPQRSTUVWXYZ".to_string(), + workspace: RadrootsFarmWorkspaceRef { + pubkey: "workspace_pubkey".to_string(), + d_tag: "ABCDEFGHIJKLMNOPQRSTUV".to_string(), + }, + farm_group_id: "BCDEFGHIJKLMNOPQRSTUVW".to_string(), + owner_document_id: "DEFGHIJKLMNOPQRSTUVWXY".to_string(), + owner_document_kind: RadrootsFarmCrdtDocumentKind::FarmTask, + caption: Some("Tomatoes harvested from Patch Y.".to_string()), + url: "https://media.example.invalid/blob/sha256".to_string(), + mime_type: "image/jpeg".to_string(), + sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + .to_string(), + original_sha256: Some( + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" + .to_string(), + ), + size_bytes: Some(123_456), + dimensions: Some(RadrootsFarmFileDimensions { w: 1600, h: 1200 }), + blurhash: Some("LEHV6nWB2yk8pyo0adR*.7kCMdnj".to_string()), + thumb: Some(RadrootsFarmFileSource { + url: "https://media.example.invalid/thumb/sha256".to_string(), + mime_type: Some("image/jpeg".to_string()), + dimensions: Some(RadrootsFarmFileDimensions { w: 320, h: 240 }), + }), + image: None, + alt: Some("Harvested tomatoes in a crate".to_string()), + fallbacks: vec!["https://fallback.example.invalid/blob/sha256".to_string()], + } + } +} diff --git a/crates/events/src/http_auth.rs b/crates/events/src/http_auth.rs @@ -0,0 +1,56 @@ +#![forbid(unsafe_code)] + +use crate::kinds::KIND_HTTP_AUTH as KIND_HTTP_AUTH_EVENT; + +#[cfg(not(feature = "std"))] +use alloc::string::String; + +pub const KIND_HTTP_AUTH: u32 = KIND_HTTP_AUTH_EVENT; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsHttpAuth { + pub url: String, + pub method: String, + pub payload_sha256: Option<String>, +} + +#[cfg(all(test, feature = "serde"))] +mod tests { + use super::*; + + #[test] + fn http_auth_kind_matches_nip98() { + assert_eq!(KIND_HTTP_AUTH, 27235); + } + + #[test] + fn http_auth_serializes_optional_payload_hash() { + let value = serde_json::to_value(RadrootsHttpAuth { + url: "https://media.example.invalid/upload".to_string(), + method: "POST".to_string(), + payload_sha256: Some( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(), + ), + }) + .unwrap(); + + assert_eq!(value["url"], "https://media.example.invalid/upload"); + assert_eq!(value["method"], "POST"); + assert_eq!( + value["payload_sha256"], + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + ); + } + + #[test] + fn http_auth_allows_absent_payload_hash() { + let auth = RadrootsHttpAuth { + url: "https://media.example.invalid/download".to_string(), + method: "GET".to_string(), + payload_sha256: None, + }; + + assert_eq!(auth.payload_sha256, None); + } +} diff --git a/crates/events/src/lib.rs b/crates/events/src/lib.rs @@ -17,6 +17,7 @@ pub mod farm_workspace; pub mod follow; pub mod geochat; pub mod gift_wrap; +pub mod http_auth; pub mod job; pub mod job_feedback; pub mod job_request; @@ -31,6 +32,7 @@ pub mod plot; pub mod post; pub mod profile; pub mod reaction; +pub mod relay_auth; pub mod relay_document; pub mod resource_area; pub mod resource_cap; diff --git a/crates/events/src/relay_auth.rs b/crates/events/src/relay_auth.rs @@ -0,0 +1,40 @@ +#![forbid(unsafe_code)] + +use crate::kinds::KIND_RELAY_AUTH as KIND_RELAY_AUTH_EVENT; + +#[cfg(not(feature = "std"))] +use alloc::string::String; + +pub const KIND_RELAY_AUTH: u32 = KIND_RELAY_AUTH_EVENT; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsRelayAuth { + pub relay: String, + pub challenge: String, +} + +#[cfg(all(test, feature = "serde"))] +mod tests { + use super::*; + + #[test] + fn relay_auth_kind_matches_nip42() { + assert_eq!(KIND_RELAY_AUTH, 22242); + } + + #[test] + fn relay_auth_serializes_nip42_tags() { + let value = serde_json::to_value(RadrootsRelayAuth { + relay: "wss://relay.example.invalid/farm/ABCDEFGHIJKLMNOPQRSTUV".to_string(), + challenge: "relay-provided-challenge".to_string(), + }) + .unwrap(); + + assert_eq!( + value["relay"], + "wss://relay.example.invalid/farm/ABCDEFGHIJKLMNOPQRSTUV" + ); + assert_eq!(value["challenge"], "relay-provided-challenge"); + } +}