nostralink 0.2.1

Linked data library for nostr
Documentation
//! Follow API

use super::manager::{RdfEventsStore, RdfStoreError};
use super::util::subl;
use crate::niri::{FromNamedNode, ToNamedNode};
use crate::oxistore::result_set::*;
use crate::querydb::nrq_get;
use nostr::PublicKey;
use oxigraph::model::vocab::rdf;
use oxigraph::model::*;
use std::sync::Arc;

pub trait FollowListManager {
    fn follow_list_node(
        &self,
        pubk: PublicKey,
        name: Option<&str>,
    ) -> Result<NamedNode, RdfStoreError>;

    fn follow_list_add(
        &self,
        flist_node: NamedNode,
        followed_pubk: PublicKey,
    ) -> Result<(), RdfStoreError>;
}

/// Follow packs
pub trait FollowPackManager {
    /// Follow/track a follow pack by its UUID
    fn fpack_follow(
        &self,
        follower_pubk: &PublicKey,
        uuid: &str,
    ) -> Result<(), RdfStoreError>;

    /// Follow pack sync
    fn fpack_members(
        &self,
        uuid: &str,
    ) -> Result<Vec<PublicKey>, RdfStoreError>;

    /// Follow pack sync
    fn fpack_sync(
        &self,
        follower_pubk: &PublicKey,
        uuid: &str,
    ) -> Result<(), RdfStoreError>;

    /// Return a list of follow packs UUIDs that a pubk follows
    fn fpack_following(
        &self,
        follower_pubk: &PublicKey,
    ) -> Result<Vec<String>, RdfStoreError>;
}

pub trait FolloweesManager {
    // Follow someone
    fn follow(
        &self,
        follower_pubk: PublicKey,
        followed_pubk: PublicKey,
    ) -> Result<(), RdfStoreError>;

    // Returns true is we're following this pubk, false otherwise
    fn following(
        &self,
        follower_pubk: PublicKey,
        followed_pubk: PublicKey,
    ) -> Result<bool, RdfStoreError>;

    // Unollow someone
    fn unfollow(
        &self,
        follower_pubk: PublicKey,
        followed_pubk: PublicKey,
    ) -> Result<(), RdfStoreError>;

    fn get_followees(
        &self,
        follower_pubk: PublicKey,
    ) -> Result<Arc<RdfResultSet>, RdfStoreError>;

    // Returns a list of followees for the given PublicKey, as a list of PublicKey
    fn followees_pubkeys(
        &self,
        follower_pubk: PublicKey,
    ) -> Result<Vec<PublicKey>, RdfStoreError>;
}

// "follows" predicate
fn pred_follows() -> NamedNode {
    NamedNode::new_unchecked("https://w3id.org/nostr#follows")
}

// "follows" predicate
fn pred_fpack_follow() -> NamedNode {
    NamedNode::new_unchecked("https://w3id.org/nostr#follows_pack")
}

#[allow(dead_code)]
fn pred_fl_owner() -> NamedNode {
    NamedNode::new_unchecked("https://w3id.org/nostr#followListOwner")
}

fn list_type() -> NamedNode {
    NamedNode::new_unchecked("http://nostralink.org/FollowList")
}

impl FollowListManager for RdfEventsStore {
    fn follow_list_node(
        &self,
        pubk: PublicKey,
        name: Option<&str>,
    ) -> Result<NamedNode, RdfStoreError> {
        let fl_nn = pubk.named_node_with_f(name.unwrap_or("fl-default"))?;

        let _ = self.store.insert(QuadRef::new(
            &fl_nn,
            rdf::TYPE,
            &list_type(),
            &GraphName::DefaultGraph,
        ));
        Ok(fl_nn)
    }

    fn follow_list_add(
        &self,
        flist_node: NamedNode,
        followed_pubk: PublicKey,
    ) -> Result<(), RdfStoreError> {
        let followed_nn = followed_pubk.named_node()?;

        let _ = self.store.insert(QuadRef::new(
            &flist_node,
            &pred_follows(),
            &followed_nn,
            &GraphName::DefaultGraph,
        ));
        Ok(())
    }
}

impl FolloweesManager for RdfEventsStore {
    fn follow(
        &self,
        follower_pubk: PublicKey,
        followee_pubk: PublicKey,
    ) -> Result<(), RdfStoreError> {
        let follower_nn = follower_pubk.named_node()?;
        let followee_nn = followee_pubk.named_node()?;

        let _ = self.store.insert(QuadRef::new(
            &follower_nn,
            &pred_follows(),
            &followee_nn,
            &GraphName::DefaultGraph,
        ));
        Ok(())
    }

    fn following(
        &self,
        follower_pubk: PublicKey,
        followee_pubk: PublicKey,
    ) -> Result<bool, RdfStoreError> {
        let follower_nn = follower_pubk.named_node()?;
        let followee_nn = followee_pubk.named_node()?;

        let results = self
            .store
            .quads_for_pattern(
                Some((&follower_nn).into()),
                Some((&pred_follows()).into()),
                Some((&followee_nn).into()),
                None,
            )
            .collect::<Result<Vec<_>, _>>()
            .map_err(|_| RdfStoreError::QueryError)?;

        Ok(results.len() > 0)
    }

    fn unfollow(
        &self,
        follower_pubk: PublicKey,
        followee_pubk: PublicKey,
    ) -> Result<(), RdfStoreError> {
        let follower_nn = follower_pubk.named_node()?;
        let followee_nn = followee_pubk.named_node()?;

        let _ = self.store.remove(QuadRef::new(
            &follower_nn,
            &pred_follows(),
            &followee_nn,
            &GraphName::DefaultGraph,
        ));
        Ok(())
    }

    fn get_followees(
        &self,
        follower_pubk: PublicKey,
    ) -> Result<Arc<RdfResultSet>, RdfStoreError> {
        let follower_nn = follower_pubk.named_node()?;

        let subs = [(Variable::new("follower").unwrap(), follower_nn.into())];

        Ok(self
            .run_query(&nrq_get("followees").unwrap(), subs, None)
            .map_err(|_| RdfStoreError::QueryError)?)
    }

    fn followees_pubkeys(
        &self,
        follower_pubk: PublicKey,
    ) -> Result<Vec<PublicKey>, RdfStoreError> {
        let follower_nn = follower_pubk.named_node()?;

        let results = self
            .store
            .quads_for_pattern(
                Some((&follower_nn).into()),
                Some((&pred_follows()).into()),
                None,
                None,
            )
            .collect::<Result<Vec<_>, _>>()
            .map_err(|_| RdfStoreError::QueryError)?;

        Ok(results
            .iter()
            .filter_map(|quad| match &quad.object {
                Term::NamedNode(node) => match node.from_named_node() {
                    Ok(pubk) => Some(pubk),
                    Err(_) => None,
                },
                _ => None,
            })
            .collect())
    }
}

impl FollowPackManager for RdfEventsStore {
    fn fpack_follow(
        &self,
        follower_pubk: &PublicKey,
        uuid: &str,
    ) -> Result<(), RdfStoreError> {
        let follower_nn = follower_pubk.named_node()?;

        let _ = self.store.insert(QuadRef::new(
            &follower_nn,
            &pred_fpack_follow(),
            &Literal::from(uuid),
            &GraphName::DefaultGraph,
        ));

        Ok(())
    }

    fn fpack_members(
        &self,
        uuid: &str,
    ) -> Result<Vec<PublicKey>, RdfStoreError> {
        let subs = [subl("uuid", Literal::from(uuid))?];

        Ok(self
            .run_query(&nrq_get("follow_pack_members").unwrap(), subs, None)
            .map_err(|_| RdfStoreError::QueryError)?
            .rows
            .iter()
            .filter_map(|row| {
                let m_pubk = row.get("member_pubk").unwrap();
                match m_pubk.value.to_public_key() {
                    Ok(pubk) => Some(pubk),
                    Err(_) => None,
                }
            })
            .collect())
    }

    fn fpack_sync(
        &self,
        follower_pubk: &PublicKey,
        uuid: &str,
    ) -> Result<(), RdfStoreError> {
        for pubk in self.fpack_members(uuid)? {
            let _ = self.follow(*follower_pubk, pubk);
        }

        Ok(())
    }

    fn fpack_following(
        &self,
        follower_pubk: &PublicKey,
    ) -> Result<Vec<String>, RdfStoreError> {
        let follower_nn = follower_pubk.named_node()?;

        let results = self
            .store
            .quads_for_pattern(
                Some((&follower_nn).into()),
                Some((&pred_fpack_follow()).into()),
                None,
                None,
            )
            .collect::<Result<Vec<_>, _>>()
            .map_err(|_| RdfStoreError::QueryError)?;

        Ok(results
            .iter()
            .filter_map(|quad| match &quad.object {
                Term::Literal(uuid) => Some(uuid.value().to_string()),
                _ => None,
            })
            .collect())
    }
}