radicle-feed 0.5.0

A feed service for Radicle
Documentation
use std::str::FromStr;

use radicle::cob::stream::{CobRange, CobStream, Stream};
use radicle::cob::{ObjectId, Op, TypeName};
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_id: Option<&str>,
    ) -> 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_id_str) = since_id {
            tracing::debug!("{} Last processed operation ID: {}", self.id, since_id_str);
            let since_oid =
                radicle::git::Oid::from_str(since_id_str).whatever_context("Invalid since_id")?;

            stream
                .since(since_oid.into())
                .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(),
        }
    }
}