radicle-feed 0.5.3

A feed service for Radicle
Documentation
use radicle::cob::stream::{CobRange, CobStream, Stream};
use radicle::cob::{ObjectId, Op, TypeName};
use radicle::git::Oid;
use snafu::ResultExt;

use crate::models::activity_feed::ActivityFeedOperation;

// Handles COB stream operations
pub struct CobStreamProcessor<'a> {
    git2_repo: &'a radicle::git::raw::Repository,
    typename: &'a TypeName,
    id: &'a ObjectId,
}

impl<'a> CobStreamProcessor<'a> {
    pub fn new(
        git2_repo: &'a radicle::git::raw::Repository,
        typename: &'a TypeName,
        id: &'a ObjectId,
    ) -> Self {
        Self {
            git2_repo,
            typename,
            id,
        }
    }

    pub fn fetch_entries_since<A>(
        &self,
        since_oid: Option<Oid>,
    ) -> Result<Vec<Op<A>>, snafu::Whatever>
    where
        A: serde::Serialize + for<'de> serde::Deserialize<'de> + Clone,
    {
        let stream = Stream::<A>::new(
            self.git2_repo,
            CobRange::new(self.typename, self.id),
            self.typename.clone(),
        );

        let entries = if let Some(since_oid) = since_oid {
            tracing::debug!("{} Last processed operation ID: {:?}", self.id, since_oid);

            stream
                .since(since_oid)
                .whatever_context("Unable to create since cob stream")?
                .filter_map(|s| s.ok())
                .filter(|entry| entry.id() != since_oid.into())
                .collect()
        } else {
            tracing::debug!(
                "{} No previous operations found (first time processing)",
                self.id
            );
            stream
                .all()
                .whatever_context("Unable to create all cob stream")?
                .filter_map(|s| s.ok())
                .collect()
        };

        Ok(entries)
    }
}

// Handles the accumulation of COB state across operations
pub struct CobStateAccumulator {
    title: String,
    state: String,
}

impl CobStateAccumulator {
    pub fn new() -> Self {
        Self {
            title: String::new(),
            state: String::from("open"),
        }
    }

    pub fn title(&self) -> String {
        self.title.clone()
    }

    pub fn state(&self) -> String {
        self.state.clone()
    }

    pub fn update_from_action(&mut self, action_json: &serde_json::Value) {
        if let Some(state) = action_json
            .get("state")
            .and_then(|t| t.get("status"))
            .and_then(|s| s.as_str())
        {
            self.state = state.to_string();
        }
        if let Some(title) = action_json.get("title").and_then(|t| t.as_str()) {
            self.title = title.to_string();
        }
    }
}

impl From<ActivityFeedOperation> for CobStateAccumulator {
    fn from(value: ActivityFeedOperation) -> Self {
        Self {
            title: value.title().to_string(),
            state: value.status().to_string(),
        }
    }
}