Phase 2c: remove audience + PostPush + PostNotification + AudienceRequest/Response
v0.6.2 wire fork: every persona-identifying direct push is gone. Public posts propagate only through the CDN (pull + header-diff neighbor propagation). Encrypted posts propagate only through pull with merged author-or-recipient match. There is no remaining sender→recipient traffic correlation signal on the wire for content. Protocol (network-breaking): - Retire MessageType 0x42 (PostNotification), 0x43 (PostPush), 0x44 (AudienceRequest), 0x45 (AudienceResponse). Their payload structs are deleted along with the handlers and senders. - SocialDisconnectNotice (0x71) / SocialAddressUpdate (0x70) sender functions targeting audience are deleted; the existing handlers stay (both already dead code on the send side). Core removals: - `push_to_audience`, `notify_post`, `push_delete`, `push_disconnect_to_audience`, `push_address_update_to_audience`, `send_audience_request`, `send_audience_response`, `send_to_audience` — all gone from network.rs. - `handle_post_notification` removed from connection.rs. - `request_audience`, `approve_audience`, `deny_audience`, `remove_audience`, `list_audience_members`, `list_audience` removed from Node. - `audience_pushed` step removed from post creation. - `AudienceDirection`, `AudienceStatus`, `AudienceRecord`, `AudienceApprovalMode` removed from types. - Storage: `store_audience`, `list_audience`, `list_audience_members`, `remove_audience`, `row_to_audience_record`, `audience_crud` test, the `audience` CREATE TABLE, and the audience-dependent social route rebuild branch all removed. Upgraded DBs retain the orphan `audience` table; nothing touches it. Follow-on cleanups: - `SocialRelation::Audience` + `::Mutual` collapsed into just `Follow`. The Display/FromStr impl accepts legacy "audience"/"mutual" strings from pre-v0.6.2 DBs and maps them to Follow. - Blob-eviction priority function drops the audience factor; relationship is now own-author vs followed vs other. Tests updated accordingly. - `CommentPermission::AudienceOnly` → `FollowersOnly`. Check uses the author's public follows (`list_public_follows`) rather than a separate audience table. `ModerationMode::AudienceOnly` similarly renamed. - Follow/unfollow routines simplified: no audience downgrade logic; unfollow removes the social route entirely. UI: - CLI: `audience*` commands removed. - Tauri: `AudienceDto`, `list_audience`, `list_audience_outbound`, `request_audience`, `approve_audience`, `remove_audience` commands removed from invoke_handler. Frontend: audience panel and audience/mutual badges removed; compose permission dropdown shows "Followers" instead of "Audience"; `loadAudience` is a no-op stub that hides any leftover DOM. Tests: 111 / 111 core tests pass. Breaking change: v0.6.2 nodes won't interoperate with v0.6.1 for delete propagation, visibility updates, direct post push, post notifications, or audience requests. Upgrade both ends.
This commit is contained in:
parent
36b6a466d2
commit
eabdb7ba4f
10 changed files with 98 additions and 1140 deletions
|
|
@ -4,7 +4,7 @@ use std::path::Path;
|
|||
use rusqlite::{params, Connection};
|
||||
|
||||
use crate::types::{
|
||||
Attachment, AudienceDirection, AudienceRecord, AudienceStatus, Circle, CircleProfile,
|
||||
Attachment, Circle, CircleProfile,
|
||||
CommentPolicy, DeleteRecord, FollowVisibility, GossipPeerInfo, GroupEpoch, GroupId,
|
||||
GroupKeyRecord, GroupMemberKey, InlineComment, ManifestEntry, NodeId, PeerRecord,
|
||||
PeerSlotKind, PeerWithAddress, Post, PostId, PostVisibility, PostingIdentity,
|
||||
|
|
@ -212,14 +212,8 @@ impl Storage {
|
|||
PRIMARY KEY (peer_id, neighbor_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_peer_neighbors_neighbor ON peer_neighbors(neighbor_id);
|
||||
CREATE TABLE IF NOT EXISTS audience (
|
||||
node_id BLOB NOT NULL,
|
||||
direction TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
requested_at INTEGER NOT NULL,
|
||||
approved_at INTEGER,
|
||||
PRIMARY KEY (node_id, direction)
|
||||
);
|
||||
-- v0.6.2: audience table removed. Upgraded DBs still have the
|
||||
-- orphan table; it's untouched by new code. New DBs don't get it.
|
||||
CREATE TABLE IF NOT EXISTS worm_cooldowns (
|
||||
target_id BLOB PRIMARY KEY,
|
||||
failed_at INTEGER NOT NULL
|
||||
|
|
@ -2856,111 +2850,6 @@ impl Storage {
|
|||
Ok(count > 0)
|
||||
}
|
||||
|
||||
// ---- Audience ----
|
||||
|
||||
/// Store an audience relationship.
|
||||
pub fn store_audience(
|
||||
&self,
|
||||
node_id: &NodeId,
|
||||
direction: AudienceDirection,
|
||||
status: AudienceStatus,
|
||||
) -> anyhow::Result<()> {
|
||||
let now = now_ms();
|
||||
let dir_str = match direction {
|
||||
AudienceDirection::Inbound => "inbound",
|
||||
AudienceDirection::Outbound => "outbound",
|
||||
};
|
||||
let status_str = match status {
|
||||
AudienceStatus::Pending => "pending",
|
||||
AudienceStatus::Approved => "approved",
|
||||
AudienceStatus::Denied => "denied",
|
||||
};
|
||||
let approved_at = if status == AudienceStatus::Approved {
|
||||
Some(now)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.conn.execute(
|
||||
"INSERT INTO audience (node_id, direction, status, requested_at, approved_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||
ON CONFLICT(node_id, direction) DO UPDATE SET
|
||||
status = ?3, approved_at = COALESCE(?5, audience.approved_at)",
|
||||
params![node_id.as_slice(), dir_str, status_str, now, approved_at],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get audience members by direction and status.
|
||||
pub fn list_audience(
|
||||
&self,
|
||||
direction: AudienceDirection,
|
||||
status: Option<AudienceStatus>,
|
||||
) -> anyhow::Result<Vec<AudienceRecord>> {
|
||||
let dir_str = match direction {
|
||||
AudienceDirection::Inbound => "inbound",
|
||||
AudienceDirection::Outbound => "outbound",
|
||||
};
|
||||
let (query, bind_status) = match status {
|
||||
Some(s) => {
|
||||
let s_str = match s {
|
||||
AudienceStatus::Pending => "pending",
|
||||
AudienceStatus::Approved => "approved",
|
||||
AudienceStatus::Denied => "denied",
|
||||
};
|
||||
(
|
||||
"SELECT node_id, direction, status, requested_at, approved_at FROM audience WHERE direction = ?1 AND status = ?2",
|
||||
Some(s_str),
|
||||
)
|
||||
}
|
||||
None => (
|
||||
"SELECT node_id, direction, status, requested_at, approved_at FROM audience WHERE direction = ?1",
|
||||
None,
|
||||
),
|
||||
};
|
||||
|
||||
let mut records = Vec::new();
|
||||
if let Some(status_str) = bind_status {
|
||||
let mut stmt = self.conn.prepare(query)?;
|
||||
let mut rows = stmt.query(params![dir_str, status_str])?;
|
||||
while let Some(row) = rows.next()? {
|
||||
records.push(row_to_audience_record(row)?);
|
||||
}
|
||||
} else {
|
||||
let mut stmt = self.conn.prepare(query)?;
|
||||
let mut rows = stmt.query(params![dir_str])?;
|
||||
while let Some(row) = rows.next()? {
|
||||
records.push(row_to_audience_record(row)?);
|
||||
}
|
||||
}
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
/// Get approved inbound audience members (nodes we push posts to).
|
||||
pub fn list_audience_members(&self) -> anyhow::Result<Vec<NodeId>> {
|
||||
let records = self.list_audience(
|
||||
AudienceDirection::Inbound,
|
||||
Some(AudienceStatus::Approved),
|
||||
)?;
|
||||
Ok(records.into_iter().map(|r| r.node_id).collect())
|
||||
}
|
||||
|
||||
/// Remove an audience relationship.
|
||||
pub fn remove_audience(
|
||||
&self,
|
||||
node_id: &NodeId,
|
||||
direction: AudienceDirection,
|
||||
) -> anyhow::Result<()> {
|
||||
let dir_str = match direction {
|
||||
AudienceDirection::Inbound => "inbound",
|
||||
AudienceDirection::Outbound => "outbound",
|
||||
};
|
||||
self.conn.execute(
|
||||
"DELETE FROM audience WHERE node_id = ?1 AND direction = ?2",
|
||||
params![node_id.as_slice(), dir_str],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---- Reach: N2/N3 ----
|
||||
|
||||
/// Replace a peer's entire N1 set in reachable_n2 (their N1 share → our N2).
|
||||
|
|
@ -3607,32 +3496,18 @@ impl Storage {
|
|||
Ok(count > 0)
|
||||
}
|
||||
|
||||
/// Bulk-populate social_routes from follows + audience + peers.
|
||||
/// Bulk-populate social_routes from follows + peers.
|
||||
/// Returns the number of routes created/updated.
|
||||
pub fn rebuild_social_routes(&self) -> anyhow::Result<usize> {
|
||||
let now = now_ms() as u64;
|
||||
let mut count = 0;
|
||||
|
||||
// Collect follows
|
||||
// v0.6.2: audience removed; social routes are built purely from follows.
|
||||
let follows: std::collections::HashSet<NodeId> =
|
||||
self.list_follows()?.into_iter().collect();
|
||||
|
||||
// Collect approved audience members (inbound = they are in our audience)
|
||||
let audience_members: std::collections::HashSet<NodeId> =
|
||||
self.list_audience_members()?.into_iter().collect();
|
||||
|
||||
// Union of all social contacts
|
||||
let mut all_contacts: std::collections::HashSet<NodeId> = std::collections::HashSet::new();
|
||||
all_contacts.extend(&follows);
|
||||
all_contacts.extend(&audience_members);
|
||||
|
||||
for nid in all_contacts {
|
||||
let relation = match (follows.contains(&nid), audience_members.contains(&nid)) {
|
||||
(true, true) => SocialRelation::Mutual,
|
||||
(true, false) => SocialRelation::Follow,
|
||||
(false, true) => SocialRelation::Audience,
|
||||
(false, false) => continue,
|
||||
};
|
||||
for nid in follows {
|
||||
let relation = SocialRelation::Follow;
|
||||
|
||||
// Look up addresses from peers table
|
||||
let addresses: Vec<std::net::SocketAddr> = self
|
||||
|
|
@ -4900,30 +4775,6 @@ fn now_ms() -> i64 {
|
|||
.as_millis() as i64
|
||||
}
|
||||
|
||||
fn row_to_audience_record(row: &rusqlite::Row) -> anyhow::Result<AudienceRecord> {
|
||||
let node_id = blob_to_nodeid(row.get(0)?)?;
|
||||
let dir_str: String = row.get(1)?;
|
||||
let status_str: String = row.get(2)?;
|
||||
let requested_at = row.get::<_, i64>(3)? as u64;
|
||||
let approved_at: Option<i64> = row.get(4)?;
|
||||
let direction = match dir_str.as_str() {
|
||||
"inbound" => AudienceDirection::Inbound,
|
||||
_ => AudienceDirection::Outbound,
|
||||
};
|
||||
let status = match status_str.as_str() {
|
||||
"approved" => AudienceStatus::Approved,
|
||||
"denied" => AudienceStatus::Denied,
|
||||
_ => AudienceStatus::Pending,
|
||||
};
|
||||
Ok(AudienceRecord {
|
||||
node_id,
|
||||
direction,
|
||||
status,
|
||||
requested_at,
|
||||
approved_at: approved_at.map(|v| v as u64),
|
||||
})
|
||||
}
|
||||
|
||||
fn row_to_peer_record(row: &rusqlite::Row) -> anyhow::Result<PeerRecord> {
|
||||
let node_id = blob_to_nodeid(row.get(0)?)?;
|
||||
let addrs_json: String = row.get(1)?;
|
||||
|
|
@ -5282,30 +5133,7 @@ mod tests {
|
|||
assert_eq!(s.count_mesh_peers_by_kind(PeerSlotKind::Local).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audience_crud() {
|
||||
use crate::types::{AudienceDirection, AudienceStatus};
|
||||
let s = temp_storage();
|
||||
let nid = make_node_id(1);
|
||||
|
||||
s.store_audience(&nid, AudienceDirection::Inbound, AudienceStatus::Pending).unwrap();
|
||||
let pending = s.list_audience(AudienceDirection::Inbound, Some(AudienceStatus::Pending)).unwrap();
|
||||
assert_eq!(pending.len(), 1);
|
||||
assert_eq!(pending[0].status, AudienceStatus::Pending);
|
||||
|
||||
// Approve
|
||||
s.store_audience(&nid, AudienceDirection::Inbound, AudienceStatus::Approved).unwrap();
|
||||
let members = s.list_audience_members().unwrap();
|
||||
assert_eq!(members.len(), 1);
|
||||
assert_eq!(members[0], nid);
|
||||
|
||||
// Remove
|
||||
s.remove_audience(&nid, AudienceDirection::Inbound).unwrap();
|
||||
let members = s.list_audience_members().unwrap();
|
||||
assert!(members.is_empty());
|
||||
}
|
||||
|
||||
// ---- Social routes tests ----
|
||||
// ---- Social routes tests ----
|
||||
|
||||
#[test]
|
||||
fn social_route_crud() {
|
||||
|
|
@ -5354,28 +5182,21 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn social_route_rebuild() {
|
||||
use crate::types::{AudienceDirection, AudienceStatus, SocialRelation};
|
||||
use crate::types::SocialRelation;
|
||||
let s = temp_storage();
|
||||
let follow_nid = make_node_id(1);
|
||||
let audience_nid = make_node_id(2);
|
||||
let mutual_nid = make_node_id(3);
|
||||
let follow_a = make_node_id(1);
|
||||
let follow_b = make_node_id(2);
|
||||
|
||||
s.add_follow(&follow_nid).unwrap();
|
||||
s.add_follow(&mutual_nid).unwrap();
|
||||
s.store_audience(&audience_nid, AudienceDirection::Inbound, AudienceStatus::Approved).unwrap();
|
||||
s.store_audience(&mutual_nid, AudienceDirection::Inbound, AudienceStatus::Approved).unwrap();
|
||||
s.add_follow(&follow_a).unwrap();
|
||||
s.add_follow(&follow_b).unwrap();
|
||||
|
||||
let count = s.rebuild_social_routes().unwrap();
|
||||
assert_eq!(count, 3);
|
||||
assert_eq!(count, 2);
|
||||
|
||||
let follow_route = s.get_social_route(&follow_nid).unwrap().unwrap();
|
||||
assert_eq!(follow_route.relation, SocialRelation::Follow);
|
||||
|
||||
let audience_route = s.get_social_route(&audience_nid).unwrap().unwrap();
|
||||
assert_eq!(audience_route.relation, SocialRelation::Audience);
|
||||
|
||||
let mutual_route = s.get_social_route(&mutual_nid).unwrap().unwrap();
|
||||
assert_eq!(mutual_route.relation, SocialRelation::Mutual);
|
||||
let route_a = s.get_social_route(&follow_a).unwrap().unwrap();
|
||||
assert_eq!(route_a.relation, SocialRelation::Follow);
|
||||
let route_b = s.get_social_route(&follow_b).unwrap().unwrap();
|
||||
assert_eq!(route_b.relation, SocialRelation::Follow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -6252,7 +6073,7 @@ mod tests {
|
|||
assert!(s.get_comment_policy(&post_id).unwrap().is_none());
|
||||
|
||||
let policy = CommentPolicy {
|
||||
allow_comments: CommentPermission::AudienceOnly,
|
||||
allow_comments: CommentPermission::FollowersOnly,
|
||||
allow_reacts: ReactPermission::Public,
|
||||
moderation: ModerationMode::AuthorBlocklist,
|
||||
blocklist: vec![make_node_id(99)],
|
||||
|
|
@ -6260,7 +6081,7 @@ mod tests {
|
|||
s.set_comment_policy(&post_id, &policy).unwrap();
|
||||
|
||||
let loaded = s.get_comment_policy(&post_id).unwrap().unwrap();
|
||||
assert_eq!(loaded.allow_comments, CommentPermission::AudienceOnly);
|
||||
assert_eq!(loaded.allow_comments, CommentPermission::FollowersOnly);
|
||||
assert_eq!(loaded.allow_reacts, ReactPermission::Public);
|
||||
assert_eq!(loaded.blocklist.len(), 1);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue