nostralink 0.2.3

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>;

    /// UnFollow a follow pack by its UUID
    fn fpack_unfollow(
        &self,
        follower_pubk: &PublicKey,
        uuid: &str,
        unfollow_members: bool,
    ) -> Result<bool, RdfStoreError>;

    /// Is this pubkey following this follow pack ?
    fn fpack_followed(
        &self,
        follower_pubk: &PublicKey,
        uuid: &str,
    ) -> Result<bool, RdfStoreError>;

    /// Return the list of public keys for the members of this follow pack
    fn fpack_members(
        &self,
        uuid: &str,
    ) -> Result<Vec<PublicKey>, RdfStoreError>;

    /// Synchronize follow pack for this follower
    /// Since follow packs can be edited (new members can be added), there needs to be
    /// a method to follow everyone in the latest edition of the follow pack
    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
const PRED_FOLLOWS: NamedNodeRef<'static> =
    NamedNodeRef::new_unchecked("https://w3id.org/nostr#follows");

// follow pack follows predicate
const PRED_FPACK_FOLLOWS: NamedNodeRef<'static> =
    NamedNodeRef::new_unchecked("https://w3id.org/nostr#follows_pack");

const PRED_FLIST_TYPE: NamedNodeRef<'static> =
    NamedNodeRef::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,
            PRED_FLIST_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),
                Some((&followee_nn).into()),
                None,
            )
            .collect::<Result<Vec<_>, _>>()
            .map_err(|_| RdfStoreError::QueryError)?;

        Ok(!results.is_empty())
    }

    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())];

        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),
                None,
                None,
            )
            .collect::<Result<Vec<_>, _>>()
            .map_err(|_| RdfStoreError::QueryError)?;

        Ok(results
            .iter()
            .filter_map(|quad| match &quad.object {
                Term::NamedNode(node) => node.from_named_node().ok(),
                _ => 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_FOLLOWS,
            &Literal::from(uuid),
            &GraphName::DefaultGraph,
        ));

        self.fpack_sync(follower_pubk, uuid)
    }

    fn fpack_unfollow(
        &self,
        follower_pubk: &PublicKey,
        uuid: &str,
        unfollow_members: bool,
    ) -> Result<bool, RdfStoreError> {
        let follower_nn = follower_pubk.named_node()?;

        if unfollow_members {
            // Unfollow every member of the follow pack
            for pubk in self.fpack_members(uuid)? {
                self.unfollow(*follower_pubk, pubk)?;
            }
        }

        self.store
            .remove(QuadRef::new(
                &follower_nn,
                PRED_FPACK_FOLLOWS,
                &Literal::from(uuid),
                &GraphName::DefaultGraph,
            ))
            .map_err(|_| RdfStoreError::QueryError)
    }

    fn fpack_followed(
        &self,
        follower_pubk: &PublicKey,
        uuid: &str,
    ) -> Result<bool, RdfStoreError> {
        let follower_nn = follower_pubk.named_node()?;

        self.store
            .contains(QuadRef::new(
                &follower_nn,
                PRED_FPACK_FOLLOWS,
                &Literal::from(uuid),
                &GraphName::DefaultGraph,
            ))
            .map_err(|_| RdfStoreError::QueryError)
    }

    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();
                m_pubk.value.to_public_key().ok()
            })
            .collect())
    }

    fn fpack_sync(
        &self,
        follower_pubk: &PublicKey,
        uuid: &str,
    ) -> Result<(), RdfStoreError> {
        for pubk in self.fpack_members(uuid)? {
            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_FOLLOWS),
                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())
    }
}