sofapub 0.1.9

A minimally functional ActivityPub server
Documentation
use serde::{Deserialize, Serialize};
use serde_json::{self, json, Value};
use std::fs::{self, OpenOptions};
use std::io::{self, Read, Write};

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum Context {
    Plain(String),
    Complex(Vec<Value>),
}

impl Default for Context {
    fn default() -> Self {
        Context::Plain("https://www.w3.org/ns/activitystreams".to_string())
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub enum CollectionType {
    #[default]
    OrderedCollection,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Collection {
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "@context")]
    pub context: Option<Context>,
    #[serde(rename = "type")]
    pub kind: CollectionType,
    pub id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub first: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prev: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub next: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub part_of: Option<String>,
    pub total_items: i32,
    pub ordered_items: Vec<Value>,
}

impl Default for Collection {
    fn default() -> Self {
        Collection {
            context: Some(Context::default()),
            kind: CollectionType::default(),
            id: None,
            first: None,
            last: None,
            prev: None,
            next: None,
            part_of: None,
            total_items: 0,
            ordered_items: vec![],
        }
    }
}

impl From<Vec<Value>> for Collection {
    fn from(values: Vec<Value>) -> Self {
        Collection {
            ordered_items: values.clone(),
            total_items: values.len() as i32,
            ..Default::default()
        }
    }
}

impl Collection {
    pub fn id(&mut self, id: String) -> &Self {
        self.id = Some(id);

        self
    }
}

fn read_collection_from_file(filename: &str) -> io::Result<Collection> {
    let path = format!(
        "{}/.sofapub/data/{}",
        std::env::var("HOME").unwrap(),
        filename
    );

    let mut file_content = String::new();
    fs::File::open(path)?.read_to_string(&mut file_content)?;

    let ordered_items: Vec<Value> = serde_json::from_str(&file_content)?;
    let collection = Collection::from(ordered_items);

    Ok(collection)
}

fn write_collection_to_file(collection: &Collection, filename: &str) -> io::Result<()> {
    let path = format!(
        "{}/.sofapub/data/{}",
        std::env::var("HOME").unwrap(),
        filename
    );

    // Only serialize the ordered_items for writing to file
    let content = serde_json::to_string_pretty(&collection.ordered_items)?;

    let mut file = OpenOptions::new().write(true).truncate(true).open(path)?;
    file.write_all(content.as_bytes())?;

    Ok(())
}

pub fn add_id_to_collection(id: String, filename: &str) -> io::Result<()> {
    let mut collection = read_collection_from_file(filename)?;

    if !collection.ordered_items.contains(&json!(id)) {
        collection.ordered_items.push(json!(id));
        // Compute total_items from length of ordered_items
        // This is unnecessary since the 'write' function only pulls out the ordered_items
        // field, but I'll keep it for completeness
        collection.total_items = collection.ordered_items.len() as i32;
    }

    write_collection_to_file(&collection, filename)
}

pub fn remove_id_from_collection(id: &str, filename: &str) -> io::Result<()> {
    let mut collection = read_collection_from_file(filename)?;

    collection.ordered_items.retain(|item| item != &json!(id));
    // Compute total_items from length of ordered_items
    // This is unnecessary since the 'write' function only pulls out the ordered_items
    // field, but I'll keep it for completeness
    collection.total_items = collection.ordered_items.len() as i32;

    write_collection_to_file(&collection, filename)
}