Currently federation requests + accepts + rejects + undos all work Federation blocking works also Profiles can federate their text and images, provided federation was enabled before profile creation. Looking into backfilling existing profiles is TODO
363 lines
11 KiB
Rust
363 lines
11 KiB
Rust
use crate::store::{count, StoreError, Undo};
|
|
use chrono::{DateTime, Utc};
|
|
use sled::{Db, Transactional, Tree};
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Relationship {
|
|
pub id: Uuid,
|
|
pub published: DateTime<Utc>,
|
|
pub left: Uuid,
|
|
pub right: Uuid,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RelationshipStore {
|
|
name: String,
|
|
|
|
id_left: Tree,
|
|
id_right: Tree,
|
|
id_published: Tree,
|
|
|
|
forward_id: Tree,
|
|
forward_counts: Tree,
|
|
forward_published_id: Tree,
|
|
|
|
backward_id: Tree,
|
|
backward_counts: Tree,
|
|
backward_published_id: Tree,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct RelationshipKeys {
|
|
name: String,
|
|
}
|
|
|
|
impl RelationshipKeys {
|
|
fn id_key(&self, id: Uuid) -> String {
|
|
format!("/{}/id/{}/data", self.name, id)
|
|
}
|
|
|
|
fn forward_key(&self, left: Uuid, right: Uuid) -> String {
|
|
format!("/{}/id/{}/forward/{}", self.name, left, right)
|
|
}
|
|
|
|
fn forward_published_key(&self, left: Uuid, published: DateTime<Utc>, right: Uuid) -> String {
|
|
format!(
|
|
"/{}/id/{}/forward/published/{}/{}",
|
|
self.name,
|
|
left,
|
|
published.to_rfc3339(),
|
|
right
|
|
)
|
|
}
|
|
|
|
fn forward_published_prefix(&self, left: Uuid) -> String {
|
|
format!("/{}/id/{}/forward/published", self.name, left)
|
|
}
|
|
|
|
fn forward_counts_key(&self, left: Uuid) -> String {
|
|
format!("/{}/id/{}/forward/counts", self.name, left)
|
|
}
|
|
|
|
fn backward_key(&self, right: Uuid, left: Uuid) -> String {
|
|
format!("/{}/id/{}/backward/{}", self.name, right, left)
|
|
}
|
|
|
|
fn backward_published_key(&self, right: Uuid, published: DateTime<Utc>, left: Uuid) -> String {
|
|
format!(
|
|
"/{}/id/{}/backward/published/{}/{}",
|
|
self.name,
|
|
right,
|
|
published.to_rfc3339(),
|
|
left
|
|
)
|
|
}
|
|
|
|
fn backward_published_prefix(&self, right: Uuid) -> String {
|
|
format!("/{}/id/{}/backward/published", self.name, right)
|
|
}
|
|
|
|
fn backward_counts_key(&self, right: Uuid) -> String {
|
|
format!("/{}/id/{}/backward/counts", self.name, right)
|
|
}
|
|
}
|
|
|
|
impl RelationshipStore {
|
|
fn keys(&self) -> RelationshipKeys {
|
|
RelationshipKeys {
|
|
name: self.name.clone(),
|
|
}
|
|
}
|
|
|
|
pub(super) fn build(name: &str, db: &Db) -> Result<Self, sled::Error> {
|
|
Ok(RelationshipStore {
|
|
id_left: db.open_tree(&format!("/profiles/relationship/{}/id/left", name))?,
|
|
id_right: db.open_tree(&format!("/profiles/relationship/{}/id/right", name))?,
|
|
id_published: db.open_tree(&format!("/profiles/relationship/{}/id/published", name))?,
|
|
|
|
forward_id: db.open_tree(&format!("/profiles/relationship/{}/forward/id", name))?,
|
|
forward_published_id: db.open_tree(&format!(
|
|
"/profiles/relationship/{}/forward/published/id",
|
|
name
|
|
))?,
|
|
forward_counts: db
|
|
.open_tree(&format!("/profiles/relationship/{}/forward/counts", name))?,
|
|
|
|
backward_id: db.open_tree(&format!("/profiles/relationship/{}/backward/id", name))?,
|
|
backward_published_id: db.open_tree(&format!(
|
|
"/profiles/relationship/{}/backward/published/id",
|
|
name
|
|
))?,
|
|
backward_counts: db
|
|
.open_tree(&format!("/profiles/relationship/{}/backward/counts", name))?,
|
|
|
|
name: name.to_owned(),
|
|
})
|
|
}
|
|
|
|
pub fn new(
|
|
&self,
|
|
left_id: Uuid,
|
|
right_id: Uuid,
|
|
published: DateTime<Utc>,
|
|
) -> Result<Relationship, StoreError> {
|
|
let keys = self.keys();
|
|
|
|
let (id, published) = [
|
|
&self.id_left,
|
|
&self.id_right,
|
|
&self.id_published,
|
|
&self.forward_id,
|
|
&self.forward_published_id,
|
|
&self.forward_counts,
|
|
&self.backward_id,
|
|
&self.backward_published_id,
|
|
&self.backward_counts,
|
|
]
|
|
.transaction(move |trees| {
|
|
let id_left = &trees[0];
|
|
let id_right = &trees[1];
|
|
let id_published = &trees[2];
|
|
|
|
let forward_id = &trees[3];
|
|
let forward_published_id = &trees[4];
|
|
let forward_counts = &trees[5];
|
|
|
|
let backward_id = &trees[6];
|
|
let backward_published_id = &trees[7];
|
|
let backward_counts = &trees[8];
|
|
|
|
let opt = forward_id
|
|
.get(keys.forward_key(left_id, right_id).as_bytes())?
|
|
.and_then(|ivec| Uuid::from_slice(&ivec).ok());
|
|
let opt = match opt {
|
|
Some(id) => id_published
|
|
.get(keys.id_key(id).as_bytes())?
|
|
.and_then(|ivec| String::from_utf8_lossy(&ivec).parse::<DateTime<Utc>>().ok())
|
|
.map(|p| (id, p)),
|
|
None => None,
|
|
};
|
|
|
|
let (id, published) = if let Some((id, published)) = opt {
|
|
(id, published)
|
|
} else {
|
|
(Uuid::new_v4(), published)
|
|
};
|
|
|
|
id_left.insert(keys.id_key(id).as_bytes(), left_id.as_bytes())?;
|
|
id_right.insert(keys.id_key(id).as_bytes(), right_id.as_bytes())?;
|
|
id_published.insert(
|
|
keys.id_key(id).as_bytes(),
|
|
published.to_rfc3339().as_bytes(),
|
|
)?;
|
|
|
|
forward_id.insert(
|
|
keys.forward_key(left_id, right_id).as_bytes(),
|
|
id.as_bytes(),
|
|
)?;
|
|
backward_id.insert(
|
|
keys.backward_key(right_id, left_id).as_bytes(),
|
|
id.as_bytes(),
|
|
)?;
|
|
|
|
forward_published_id.insert(
|
|
keys.forward_published_key(left_id, published, right_id)
|
|
.as_bytes(),
|
|
right_id.as_bytes(),
|
|
)?;
|
|
backward_published_id.insert(
|
|
keys.backward_published_key(right_id, published, left_id)
|
|
.as_bytes(),
|
|
left_id.as_bytes(),
|
|
)?;
|
|
|
|
count(forward_counts, &keys.forward_counts_key(left_id), |c| {
|
|
c.saturating_add(1)
|
|
})?;
|
|
count(backward_counts, &keys.backward_counts_key(right_id), |c| {
|
|
c.saturating_add(1)
|
|
})?;
|
|
|
|
Ok((id, published))
|
|
})?;
|
|
|
|
Ok(Relationship {
|
|
id,
|
|
published,
|
|
left: left_id,
|
|
right: right_id,
|
|
})
|
|
}
|
|
|
|
pub fn forward_iter(&self, left_id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
let keys = self.keys();
|
|
|
|
self.forward_published_id
|
|
.scan_prefix(keys.forward_published_prefix(left_id))
|
|
.values()
|
|
.filter_map(|res| res.ok())
|
|
.filter_map(|ivec| Uuid::from_slice(&ivec).ok())
|
|
}
|
|
|
|
pub fn backward_iter(&self, right_id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
let keys = self.keys();
|
|
|
|
self.backward_published_id
|
|
.scan_prefix(keys.backward_published_prefix(right_id))
|
|
.values()
|
|
.filter_map(|res| res.ok())
|
|
.filter_map(|ivec| Uuid::from_slice(&ivec).ok())
|
|
}
|
|
|
|
pub fn by_forward(&self, left_id: Uuid, right_id: Uuid) -> Result<Option<Uuid>, StoreError> {
|
|
let keys = self.keys();
|
|
let opt = self
|
|
.forward_id
|
|
.get(keys.forward_key(left_id, right_id))?
|
|
.and_then(|ivec| Uuid::from_slice(&ivec).ok());
|
|
|
|
Ok(opt)
|
|
}
|
|
|
|
pub fn left(&self, id: Uuid) -> Result<Option<Uuid>, StoreError> {
|
|
let keys = self.keys();
|
|
let opt = self
|
|
.id_left
|
|
.get(keys.id_key(id))?
|
|
.and_then(|ivec| Uuid::from_slice(&ivec).ok());
|
|
Ok(opt)
|
|
}
|
|
|
|
pub fn right(&self, id: Uuid) -> Result<Option<Uuid>, StoreError> {
|
|
let keys = self.keys();
|
|
let opt = self
|
|
.id_right
|
|
.get(keys.id_key(id))?
|
|
.and_then(|ivec| Uuid::from_slice(&ivec).ok());
|
|
Ok(opt)
|
|
}
|
|
|
|
pub fn published(&self, id: Uuid) -> Result<Option<DateTime<Utc>>, StoreError> {
|
|
let keys = self.keys();
|
|
let opt = self
|
|
.id_published
|
|
.get(keys.id_key(id))?
|
|
.and_then(|ivec| String::from_utf8_lossy(&ivec).parse::<DateTime<Utc>>().ok());
|
|
Ok(opt)
|
|
}
|
|
|
|
pub fn by_id(&self, id: Uuid) -> Result<Option<Relationship>, StoreError> {
|
|
let left_opt = self.left(id)?;
|
|
let right_opt = self.right(id)?;
|
|
let published_opt = self.published(id)?;
|
|
|
|
let opt = left_opt.and_then(|left| {
|
|
right_opt.and_then(|right| {
|
|
published_opt.map(|published| Relationship {
|
|
id,
|
|
left,
|
|
right,
|
|
published,
|
|
})
|
|
})
|
|
});
|
|
|
|
Ok(opt)
|
|
}
|
|
|
|
pub fn remove(&self, id: Uuid) -> Result<Option<Undo<Relationship>>, StoreError> {
|
|
let keys = self.keys();
|
|
|
|
let left_id = match self.left(id)? {
|
|
Some(id) => id,
|
|
None => return Ok(None),
|
|
};
|
|
let right_id = match self.right(id)? {
|
|
Some(id) => id,
|
|
None => return Ok(None),
|
|
};
|
|
let published = match self.published(id)? {
|
|
Some(id) => id,
|
|
None => return Ok(None),
|
|
};
|
|
|
|
[
|
|
&self.id_left,
|
|
&self.id_right,
|
|
&self.id_published,
|
|
&self.forward_id,
|
|
&self.forward_published_id,
|
|
&self.forward_counts,
|
|
&self.backward_id,
|
|
&self.backward_published_id,
|
|
&self.backward_counts,
|
|
]
|
|
.transaction(move |trees| {
|
|
let id_left = &trees[0];
|
|
let id_right = &trees[1];
|
|
let id_published = &trees[2];
|
|
|
|
let forward_id = &trees[3];
|
|
let forward_published_id = &trees[4];
|
|
let forward_counts = &trees[5];
|
|
|
|
let backward_id = &trees[6];
|
|
let backward_published_id = &trees[7];
|
|
let backward_counts = &trees[8];
|
|
|
|
id_left.remove(keys.id_key(id).as_bytes())?;
|
|
id_right.remove(keys.id_key(id).as_bytes())?;
|
|
id_published.remove(keys.id_key(id).as_bytes())?;
|
|
|
|
forward_id.remove(keys.forward_key(left_id, right_id).as_bytes())?;
|
|
backward_id.remove(keys.backward_key(right_id, left_id).as_bytes())?;
|
|
|
|
forward_published_id.remove(
|
|
keys.forward_published_key(left_id, published, right_id)
|
|
.as_bytes(),
|
|
)?;
|
|
backward_published_id.remove(
|
|
keys.backward_published_key(right_id, published, left_id)
|
|
.as_bytes(),
|
|
)?;
|
|
|
|
count(forward_counts, &keys.forward_counts_key(left_id), |c| {
|
|
c.saturating_sub(1)
|
|
})?;
|
|
count(backward_counts, &keys.backward_counts_key(right_id), |c| {
|
|
c.saturating_sub(1)
|
|
})?;
|
|
|
|
Ok(())
|
|
})?;
|
|
|
|
Ok(Some(Undo(Relationship {
|
|
id,
|
|
left: left_id,
|
|
right: right_id,
|
|
published,
|
|
})))
|
|
}
|
|
}
|