forked from alemi/upub
feat: separated apb types into crate, reworked
no more "impl ..." hell, each trait has associated types so that we know it's a "Self::Link" or a "Self::Actor", but in practice they can both be a "serde_json::Value" and thus we can change its type. also Node::Array is now a Vec<T> rather than Vec<Node<T>> because it makes more sense. Node is Iterable and will yield zero (Empty|Link), one (Object) or many (Array) Ts
This commit is contained in:
parent
b5867b90ac
commit
520c8eff3a
51 changed files with 3593 additions and 829 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
/apb/target
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
[workspace]
|
||||||
|
members = ["apb"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "upub"
|
name = "upub"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -10,7 +13,7 @@ axum = "0.7.3"
|
||||||
chrono = { version = "0.4.31", features = ["serde"] }
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
clap = { version = "4.5.3", features = ["derive"] }
|
clap = { version = "4.5.3", features = ["derive"] }
|
||||||
paste = "1.0.14"
|
paste = "1.0.14"
|
||||||
reqwest = { version = "0.11.26", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
sea-orm = { version = "0.12.14", features = ["macros", "sqlx-sqlite", "runtime-tokio-rustls"] }
|
sea-orm = { version = "0.12.14", features = ["macros", "sqlx-sqlite", "runtime-tokio-rustls"] }
|
||||||
sea-orm-migration = "0.12.15"
|
sea-orm-migration = "0.12.15"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
|
@ -21,6 +24,7 @@ tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
uuid = { version = "1.8.0", features = ["v4"] }
|
uuid = { version = "1.8.0", features = ["v4"] }
|
||||||
jrd = "0.1"
|
jrd = "0.1"
|
||||||
|
apb = { path = "apb", features = ["dict", "fetch", "orm"] }
|
||||||
# nodeinfo = "0.0.2" # the version on crates.io doesn't re-export necessary types to build the struct!!!
|
# nodeinfo = "0.0.2" # the version on crates.io doesn't re-export necessary types to build the struct!!!
|
||||||
nodeinfo = { git = "https://codeberg.org/thefederationinfo/nodeinfo-rs", rev = "e865094804" }
|
nodeinfo = { git = "https://codeberg.org/thefederationinfo/nodeinfo-rs", rev = "e865094804" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
2747
apb/Cargo.lock
generated
Normal file
2747
apb/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
23
apb/Cargo.toml
Normal file
23
apb/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "apb"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
thiserror = "1.0"
|
||||||
|
serde_json = { version = "1.0", optional = true }
|
||||||
|
reqwest = { version = "0.12", features = ["json"], optional = true }
|
||||||
|
paste = "1.0.14"
|
||||||
|
sea-orm = { version = "0.12", optional = true }
|
||||||
|
tracing = "0.1.40"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["fetch", "dict", "orm"]
|
||||||
|
fetch = ["dep:reqwest"]
|
||||||
|
dict = ["dep:serde_json"]
|
||||||
|
orm = ["dep:sea-orm"]
|
41
apb/src/base.rs
Normal file
41
apb/src/base.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::{getter, setter, strenum, LinkType, ObjectType};
|
||||||
|
|
||||||
|
strenum! {
|
||||||
|
pub enum BaseType {
|
||||||
|
;
|
||||||
|
Object(ObjectType),
|
||||||
|
Link(LinkType)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Base {
|
||||||
|
fn id(&self) -> Option<&str> { None }
|
||||||
|
fn base_type(&self) -> Option<BaseType> { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub trait BaseMut {
|
||||||
|
fn set_id(self, val: Option<&str>) -> Self;
|
||||||
|
fn set_base_type(self, val: Option<BaseType>) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Base for String {
|
||||||
|
fn id(&self) -> Option<&str> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_type(&self) -> Option<BaseType> {
|
||||||
|
Some(BaseType::Link(LinkType::Link))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Base for serde_json::Value {
|
||||||
|
getter! { id -> &str }
|
||||||
|
getter! { base_type -> type BaseType }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BaseMut for serde_json::Value {
|
||||||
|
setter! { id -> &str }
|
||||||
|
setter! { base_type -> type BaseType }
|
||||||
|
}
|
36
apb/src/lib.rs
Normal file
36
apb/src/lib.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
mod node;
|
||||||
|
pub use node::Node;
|
||||||
|
|
||||||
|
mod link;
|
||||||
|
pub use link::{Link, LinkMut, LinkType};
|
||||||
|
|
||||||
|
pub mod key;
|
||||||
|
pub use key::{PublicKey, PublicKeyMut};
|
||||||
|
|
||||||
|
mod base;
|
||||||
|
pub use base::{Base, BaseMut, BaseType};
|
||||||
|
|
||||||
|
mod object;
|
||||||
|
pub use object::{
|
||||||
|
Object, ObjectMut, ObjectType,
|
||||||
|
activity::{
|
||||||
|
Activity, ActivityMut, ActivityType,
|
||||||
|
accept::{Accept, AcceptMut, AcceptType},
|
||||||
|
ignore::{Ignore, IgnoreMut, IgnoreType},
|
||||||
|
intransitive::{IntransitiveActivity, IntransitiveActivityMut, IntransitiveActivityType},
|
||||||
|
offer::{Offer, OfferMut, OfferType},
|
||||||
|
reject::{Reject, RejectMut, RejectType},
|
||||||
|
},
|
||||||
|
actor::{Actor, ActorMut, ActorType},
|
||||||
|
collection::{
|
||||||
|
Collection, CollectionMut, CollectionType,
|
||||||
|
page::{CollectionPage, CollectionPageMut}
|
||||||
|
},
|
||||||
|
document::{Document, DocumentMut, DocumentType},
|
||||||
|
place::{Place, PlaceMut},
|
||||||
|
// profile::Profile,
|
||||||
|
relationship::{Relationship, RelationshipMut},
|
||||||
|
tombstone::{Tombstone, TombstoneMut},
|
||||||
|
};
|
|
@ -37,7 +37,7 @@ macro_rules! strenum {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for $enum_name {
|
impl TryFrom<&str> for $enum_name {
|
||||||
type Error = $crate::activitystream::macros::TypeValueError;
|
type Error = $crate::macros::TypeValueError;
|
||||||
|
|
||||||
fn try_from(value:&str) -> Result<Self, Self::Error> {
|
fn try_from(value:&str) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
|
@ -48,7 +48,7 @@ macro_rules! strenum {
|
||||||
return Ok(Self::$deep(x));
|
return Ok(Self::$deep(x));
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
Err($crate::activitystream::macros::TypeValueError)
|
Err($crate::macros::TypeValueError)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,19 +171,19 @@ macro_rules! getter {
|
||||||
};
|
};
|
||||||
|
|
||||||
($name:ident -> node $t:ty) => {
|
($name:ident -> node $t:ty) => {
|
||||||
fn $name(&self) -> $crate::activitystream::Node<$t> {
|
fn $name(&self) -> $crate::Node<$t> {
|
||||||
match self.get(stringify!($name)) {
|
match self.get(stringify!($name)) {
|
||||||
Some(x) => $crate::activitystream::Node::from(x.clone()),
|
Some(x) => $crate::Node::from(x.clone()),
|
||||||
None => $crate::activitystream::Node::Empty,
|
None => $crate::Node::Empty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
($name:ident::$rename:ident -> node $t:ty) => {
|
($name:ident::$rename:ident -> node $t:ty) => {
|
||||||
fn $name(&self) -> $crate::activitystream::Node<$t> {
|
fn $name(&self) -> $crate::Node<$t> {
|
||||||
match self.get(stringify!($rename)) {
|
match self.get(stringify!($rename)) {
|
||||||
Some(x) => $crate::activitystream::Node::from(x.clone()),
|
Some(x) => $crate::Node::from(x.clone()),
|
||||||
None => $crate::activitystream::Node::Empty,
|
None => $crate::Node::Empty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -194,7 +194,7 @@ macro_rules! setter {
|
||||||
($name:ident -> bool) => {
|
($name:ident -> bool) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: Option<bool>) -> Self {
|
fn [< set_$name >](mut self, val: Option<bool>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_value(
|
$crate::macros::set_maybe_value(
|
||||||
&mut self, stringify!($name), val.map(|x| serde_json::Value::Bool(x))
|
&mut self, stringify!($name), val.map(|x| serde_json::Value::Bool(x))
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -205,7 +205,7 @@ macro_rules! setter {
|
||||||
($name:ident -> &str) => {
|
($name:ident -> &str) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: Option<&str>) -> Self {
|
fn [< set_$name >](mut self, val: Option<&str>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_value(
|
$crate::macros::set_maybe_value(
|
||||||
&mut self, stringify!($name), val.map(|x| serde_json::Value::String(x.to_string()))
|
&mut self, stringify!($name), val.map(|x| serde_json::Value::String(x.to_string()))
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -216,7 +216,7 @@ macro_rules! setter {
|
||||||
($name:ident::$rename:ident -> &str) => {
|
($name:ident::$rename:ident -> &str) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: Option<&str>) -> Self {
|
fn [< set_$name >](mut self, val: Option<&str>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_value(
|
$crate::macros::set_maybe_value(
|
||||||
&mut self, stringify!($rename), val.map(|x| serde_json::Value::String(x.to_string()))
|
&mut self, stringify!($rename), val.map(|x| serde_json::Value::String(x.to_string()))
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -227,7 +227,7 @@ macro_rules! setter {
|
||||||
($name:ident -> u64) => {
|
($name:ident -> u64) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: Option<u64>) -> Self {
|
fn [< set_$name >](mut self, val: Option<u64>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_value(
|
$crate::macros::set_maybe_value(
|
||||||
&mut self, stringify!($name), val.map(|x| serde_json::Value::Number(serde_json::Number::from(x)))
|
&mut self, stringify!($name), val.map(|x| serde_json::Value::Number(serde_json::Number::from(x)))
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -238,7 +238,7 @@ macro_rules! setter {
|
||||||
($name:ident::$rename:ident -> u64) => {
|
($name:ident::$rename:ident -> u64) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: Option<u64>) -> Self {
|
fn [< set_$name >](mut self, val: Option<u64>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_value(
|
$crate::macros::set_maybe_value(
|
||||||
&mut self, stringify!($rename), val.map(|x| serde_json::Value::Number(serde_json::Number::from(x)))
|
&mut self, stringify!($rename), val.map(|x| serde_json::Value::Number(serde_json::Number::from(x)))
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -249,7 +249,7 @@ macro_rules! setter {
|
||||||
($name:ident -> chrono::DateTime<chrono::Utc>) => {
|
($name:ident -> chrono::DateTime<chrono::Utc>) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self {
|
fn [< set_$name >](mut self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_value(
|
$crate::macros::set_maybe_value(
|
||||||
&mut self, stringify!($name), val.map(|x| serde_json::Value::String(x.to_rfc3339()))
|
&mut self, stringify!($name), val.map(|x| serde_json::Value::String(x.to_rfc3339()))
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -260,7 +260,7 @@ macro_rules! setter {
|
||||||
($name:ident::$rename:ident -> chrono::DateTime<chrono::Utc>) => {
|
($name:ident::$rename:ident -> chrono::DateTime<chrono::Utc>) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self {
|
fn [< set_$name >](mut self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_value(
|
$crate::macros::set_maybe_value(
|
||||||
&mut self, stringify!($rename), val.map(|x| serde_json::Value::String(x.to_rfc3339()))
|
&mut self, stringify!($rename), val.map(|x| serde_json::Value::String(x.to_rfc3339()))
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -270,8 +270,8 @@ macro_rules! setter {
|
||||||
|
|
||||||
($name:ident -> node $t:ty ) => {
|
($name:ident -> node $t:ty ) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: $crate::activitystream::Node<$t>) -> Self {
|
fn [< set_$name >](mut self, val: $crate::Node<$t>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_node(
|
$crate::macros::set_maybe_node(
|
||||||
&mut self, stringify!($name), val
|
&mut self, stringify!($name), val
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -281,8 +281,8 @@ macro_rules! setter {
|
||||||
|
|
||||||
($name:ident::$rename:ident -> node $t:ty ) => {
|
($name:ident::$rename:ident -> node $t:ty ) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: $crate::activitystream::Node<$t>) -> Self {
|
fn [< set_$name >](mut self, val: $crate::Node<$t>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_node(
|
$crate::macros::set_maybe_node(
|
||||||
&mut self, stringify!($rename), val
|
&mut self, stringify!($rename), val
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -293,7 +293,7 @@ macro_rules! setter {
|
||||||
($name:ident -> type $t:ty ) => {
|
($name:ident -> type $t:ty ) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
fn [< set_$name >](mut self, val: Option<$t>) -> Self {
|
fn [< set_$name >](mut self, val: Option<$t>) -> Self {
|
||||||
$crate::activitystream::macros::set_maybe_value(
|
$crate::macros::set_maybe_value(
|
||||||
&mut self, "type", val.map(|x| serde_json::Value::String(x.as_ref().to_string()))
|
&mut self, "type", val.map(|x| serde_json::Value::String(x.as_ref().to_string()))
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
|
@ -302,11 +302,11 @@ macro_rules! setter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_maybe_node<T : super::Base>(obj: &mut serde_json::Value, key: &str, node: super::Node<T>) {
|
pub fn set_maybe_node(obj: &mut serde_json::Value, key: &str, node: super::Node<serde_json::Value>) {
|
||||||
match node {
|
match node {
|
||||||
super::Node::Object(x) => {
|
super::Node::Object(x) => {
|
||||||
set_maybe_value(
|
set_maybe_value(
|
||||||
obj, key, Some(x.underlying_json_object()),
|
obj, key, Some(*x),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
super::Node::Link(l) => {
|
super::Node::Link(l) => {
|
||||||
|
@ -316,7 +316,7 @@ pub fn set_maybe_node<T : super::Base>(obj: &mut serde_json::Value, key: &str, n
|
||||||
},
|
},
|
||||||
super::Node::Array(_) => {
|
super::Node::Array(_) => {
|
||||||
set_maybe_value(
|
set_maybe_value(
|
||||||
obj, key, Some(serde_json::Value::Array(node.flat())),
|
obj, key, Some(serde_json::Value::Array(node.into_iter().collect())),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
super::Node::Empty => {
|
super::Node::Empty => {
|
||||||
|
@ -357,7 +357,7 @@ impl InsertValue for serde_json::Map<String, serde_json::Value> {
|
||||||
Node::Array(ref _arr) => {
|
Node::Array(ref _arr) => {
|
||||||
self.insert(
|
self.insert(
|
||||||
k.to_string(),
|
k.to_string(),
|
||||||
serde_json::Value::Array(node.flat()),
|
serde_json::Value::Array(node.into_iter().collect()),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Node::Link(l) => {
|
Node::Link(l) => {
|
167
apb/src/node.rs
Normal file
167
apb/src/node.rs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
pub enum Node<T : super::Base> {
|
||||||
|
Array(Vec<T>), // TODO would be cool to make it Box<[T]> so that Node is just a ptr
|
||||||
|
Object(Box<T>),
|
||||||
|
Link(Box<dyn super::Link>),
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T : super::Base> From<Option<T>> for Node<T> {
|
||||||
|
fn from(value: Option<T>) -> Self {
|
||||||
|
match value {
|
||||||
|
Some(x) => Node::Object(Box::new(x)),
|
||||||
|
None => Node::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T : super::Base + Clone> Iterator for Node<T> {
|
||||||
|
type Item = T;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let x = match self {
|
||||||
|
Self::Empty => return None,
|
||||||
|
Self::Link(_) => return None,
|
||||||
|
Self::Array(arr) => return arr.pop(), // TODO weird that we iter in reverse
|
||||||
|
Self::Object(x) => *x.clone(), // TODO needed because next() on object can't get value without owning
|
||||||
|
};
|
||||||
|
*self = Self::Empty;
|
||||||
|
Some(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T : super::Base> Node<T> {
|
||||||
|
pub fn get(&self) -> Option<&T> {
|
||||||
|
match self {
|
||||||
|
Node::Empty | Node::Link(_) => None,
|
||||||
|
Node::Object(x) => Some(x),
|
||||||
|
Node::Array(v) => v.last(), // TODO so it's coherent with next(), still weird tho!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract(self) -> Option<T> {
|
||||||
|
match self {
|
||||||
|
Node::Empty | Node::Link(_) => None,
|
||||||
|
Node::Object(x) => Some(*x),
|
||||||
|
Node::Array(mut v) => v.pop(), // TODO so it's coherent with next(), still weird tho!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
matches!(self, Node::Empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_link(&self) -> bool {
|
||||||
|
matches!(self, Node::Link(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_object(&self) -> bool {
|
||||||
|
matches!(self, Node::Object(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_array(&self) -> bool {
|
||||||
|
matches!(self, Node::Array(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Node::Empty => 0,
|
||||||
|
Node::Link(_) => 1,
|
||||||
|
Node::Object(_) => 1,
|
||||||
|
Node::Array(v) => v.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Node::Empty | Node::Array(_) => None,
|
||||||
|
Node::Link(uri) => Some(uri.href().to_string()),
|
||||||
|
Node::Object(obj) => obj.id().map(|x| x.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dict")]
|
||||||
|
impl Node<serde_json::Value> {
|
||||||
|
pub fn link(uri: String) -> Self {
|
||||||
|
Node::Link(Box::new(uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn links(uris: Vec<String>) -> Self {
|
||||||
|
Node::Array(
|
||||||
|
uris
|
||||||
|
.into_iter()
|
||||||
|
.map(serde_json::Value::String)
|
||||||
|
.collect()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maybe_link(uri: Option<String>) -> Self {
|
||||||
|
match uri {
|
||||||
|
Some(uri) => Node::Link(Box::new(uri)),
|
||||||
|
None => Node::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object(x: serde_json::Value) -> Self {
|
||||||
|
Node::Object(Box::new(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maybe_object(x: Option<serde_json::Value>) -> Self {
|
||||||
|
match x {
|
||||||
|
Some(x) => Node::Object(Box::new(x)),
|
||||||
|
None => Node::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn array(values: Vec<serde_json::Value>) -> Self {
|
||||||
|
Node::Array(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fetch")]
|
||||||
|
pub async fn fetch(&mut self) -> reqwest::Result<&mut Self> {
|
||||||
|
if let Node::Link(link) = self {
|
||||||
|
*self = reqwest::Client::new()
|
||||||
|
.get(link.href())
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json::<serde_json::Value>()
|
||||||
|
.await?
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dict")]
|
||||||
|
impl From<Option<&str>> for Node<serde_json::Value> {
|
||||||
|
fn from(value: Option<&str>) -> Self {
|
||||||
|
match value {
|
||||||
|
Some(x) => Node::Link(Box::new(x.to_string())),
|
||||||
|
None => Node::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dict")]
|
||||||
|
impl From<&str> for Node<serde_json::Value> {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Node::Link(Box::new(value.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dict")]
|
||||||
|
impl From<serde_json::Value> for Node<serde_json::Value> {
|
||||||
|
fn from(value: serde_json::Value) -> Self {
|
||||||
|
match value {
|
||||||
|
serde_json::Value::String(uri) => Node::Link(Box::new(uri)),
|
||||||
|
serde_json::Value::Array(arr) => Node::Array(arr),
|
||||||
|
serde_json::Value::Object(_) => match value.get("href") {
|
||||||
|
None => Node::Object(Box::new(value)),
|
||||||
|
Some(_) => Node::Link(Box::new(value)),
|
||||||
|
},
|
||||||
|
_ => Node::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
81
apb/src/object/activity/mod.rs
Normal file
81
apb/src/object/activity/mod.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
pub mod accept;
|
||||||
|
pub mod ignore;
|
||||||
|
pub mod intransitive;
|
||||||
|
pub mod offer;
|
||||||
|
pub mod reject;
|
||||||
|
|
||||||
|
use crate::{Node, object::{Object, ObjectMut}, getter, setter, strenum};
|
||||||
|
use accept::AcceptType;
|
||||||
|
use reject::RejectType;
|
||||||
|
use offer::OfferType;
|
||||||
|
use intransitive::IntransitiveActivityType;
|
||||||
|
use ignore::IgnoreType;
|
||||||
|
|
||||||
|
strenum! {
|
||||||
|
pub enum ActivityType {
|
||||||
|
Activity,
|
||||||
|
Add,
|
||||||
|
Announce,
|
||||||
|
Create,
|
||||||
|
Delete,
|
||||||
|
Dislike,
|
||||||
|
Flag,
|
||||||
|
Follow,
|
||||||
|
Join,
|
||||||
|
Leave,
|
||||||
|
Like,
|
||||||
|
Listen,
|
||||||
|
Move,
|
||||||
|
Read,
|
||||||
|
Remove,
|
||||||
|
Undo,
|
||||||
|
Update,
|
||||||
|
View;
|
||||||
|
|
||||||
|
IntransitiveActivity(IntransitiveActivityType),
|
||||||
|
Accept(AcceptType),
|
||||||
|
Ignore(IgnoreType),
|
||||||
|
Offer(OfferType),
|
||||||
|
Reject(RejectType)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Activity : Object {
|
||||||
|
fn activity_type(&self) -> Option<ActivityType> { None }
|
||||||
|
fn actor(&self) -> Node<Self::Actor> { Node::Empty }
|
||||||
|
fn object(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn target(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn result(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn origin(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn instrument(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ActivityMut : ObjectMut {
|
||||||
|
fn set_activity_type(self, val: Option<ActivityType>) -> Self;
|
||||||
|
fn set_actor(self, val: Node<Self::Actor>) -> Self;
|
||||||
|
fn set_object(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_target(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_result(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_origin(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_instrument(self, val: Node<Self::Object>) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Activity for serde_json::Value {
|
||||||
|
getter! { activity_type -> type ActivityType }
|
||||||
|
getter! { actor -> node Self::Actor }
|
||||||
|
getter! { object -> node <Self as Object>::Object }
|
||||||
|
getter! { target -> node <Self as Object>::Object }
|
||||||
|
getter! { result -> node <Self as Object>::Object }
|
||||||
|
getter! { origin -> node <Self as Object>::Object }
|
||||||
|
getter! { instrument -> node <Self as Object>::Object }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActivityMut for serde_json::Value {
|
||||||
|
setter! { activity_type -> type ActivityType }
|
||||||
|
setter! { actor -> node Self::Actor }
|
||||||
|
setter! { object -> node <Self as Object>::Object }
|
||||||
|
setter! { target -> node <Self as Object>::Object }
|
||||||
|
setter! { result -> node <Self as Object>::Object }
|
||||||
|
setter! { origin -> node <Self as Object>::Object }
|
||||||
|
setter! { instrument -> node <Self as Object>::Object }
|
||||||
|
}
|
84
apb/src/object/actor.rs
Normal file
84
apb/src/object/actor.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use crate::{Node, getter, setter, strenum};
|
||||||
|
|
||||||
|
use super::{Object, ObjectMut, super::key::PublicKey};
|
||||||
|
|
||||||
|
strenum! {
|
||||||
|
pub enum ActorType {
|
||||||
|
Application,
|
||||||
|
Group,
|
||||||
|
Organization,
|
||||||
|
Person;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Actor : Object {
|
||||||
|
type PublicKey : PublicKey;
|
||||||
|
|
||||||
|
fn actor_type(&self) -> Option<ActorType> { None }
|
||||||
|
fn preferred_username(&self) -> Option<&str> { None }
|
||||||
|
fn inbox(&self) -> Node<Self::Collection>;
|
||||||
|
fn outbox(&self) -> Node<Self::Collection>;
|
||||||
|
fn following(&self) -> Node<Self::Collection> { todo!() }
|
||||||
|
fn followers(&self) -> Node<Self::Collection> { todo!() }
|
||||||
|
fn liked(&self) -> Node<Self::Collection> { todo!() }
|
||||||
|
fn streams(&self) -> Node<Self::Collection> { todo!() }
|
||||||
|
fn endpoints(&self) -> Option<serde_json::Map<String, String>> { None }
|
||||||
|
fn public_key(&self) -> Node<Self::PublicKey> { todo!() }
|
||||||
|
// idk about this? everyone has it but AP doesn't mention it
|
||||||
|
fn discoverable(&self) -> Option<bool> { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ActorMut : ObjectMut {
|
||||||
|
type PublicKey : PublicKey;
|
||||||
|
|
||||||
|
fn set_actor_type(self, val: Option<ActorType>) -> Self;
|
||||||
|
fn set_preferred_username(self, val: Option<&str>) -> Self;
|
||||||
|
fn set_inbox(self, val: Node<Self::Collection>) -> Self;
|
||||||
|
fn set_outbox(self, val: Node<Self::Collection>) -> Self;
|
||||||
|
fn set_following(self, val: Node<Self::Collection>) -> Self;
|
||||||
|
fn set_followers(self, val: Node<Self::Collection>) -> Self;
|
||||||
|
fn set_liked(self, val: Node<Self::Collection>) -> Self;
|
||||||
|
fn set_streams(self, val: Node<Self::Collection>) -> Self;
|
||||||
|
fn set_endpoints(self, val: Option<serde_json::Map<String, String>>) -> Self;
|
||||||
|
fn set_public_key(self, val: Node<Self::PublicKey>) -> Self;
|
||||||
|
fn set_discoverable(self, val: Option<bool>) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for serde_json::Value {
|
||||||
|
type PublicKey = serde_json::Value;
|
||||||
|
|
||||||
|
getter! { actor_type -> type ActorType }
|
||||||
|
getter! { preferred_username::preferredUsername -> &str }
|
||||||
|
getter! { inbox -> node Self::Collection }
|
||||||
|
getter! { outbox -> node Self::Collection }
|
||||||
|
getter! { following -> node Self::Collection }
|
||||||
|
getter! { followers -> node Self::Collection }
|
||||||
|
getter! { liked -> node Self::Collection }
|
||||||
|
getter! { streams -> node Self::Collection }
|
||||||
|
getter! { public_key::publicKey -> node Self::PublicKey }
|
||||||
|
getter! { discoverable -> bool }
|
||||||
|
|
||||||
|
fn endpoints(&self) -> Option<serde_json::Map<String, String>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActorMut for serde_json::Value {
|
||||||
|
type PublicKey = serde_json::Value;
|
||||||
|
|
||||||
|
setter! { actor_type -> type ActorType }
|
||||||
|
setter! { preferred_username::preferredUsername -> &str }
|
||||||
|
setter! { inbox -> node Self::Collection }
|
||||||
|
setter! { outbox -> node Self::Collection }
|
||||||
|
setter! { following -> node Self::Collection }
|
||||||
|
setter! { followers -> node Self::Collection }
|
||||||
|
setter! { liked -> node Self::Collection }
|
||||||
|
setter! { streams -> node Self::Collection }
|
||||||
|
setter! { public_key::publicKey -> node Self::PublicKey }
|
||||||
|
setter! { discoverable -> bool }
|
||||||
|
|
||||||
|
fn set_endpoints(mut self, _val: Option<serde_json::Map<String, String>>) -> Self {
|
||||||
|
self.as_object_mut().unwrap().insert("endpoints".to_string(), serde_json::Value::Object(serde_json::Map::default()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
62
apb/src/object/collection/mod.rs
Normal file
62
apb/src/object/collection/mod.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
pub mod page;
|
||||||
|
pub use page::CollectionPage;
|
||||||
|
|
||||||
|
use crate::{Node, Object, object::ObjectMut, getter, setter, strenum};
|
||||||
|
|
||||||
|
strenum! {
|
||||||
|
pub enum CollectionType {
|
||||||
|
Collection,
|
||||||
|
CollectionPage,
|
||||||
|
OrderedCollection,
|
||||||
|
OrderedCollectionPage;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Collection : Object {
|
||||||
|
type CollectionPage : CollectionPage;
|
||||||
|
|
||||||
|
fn collection_type(&self) -> Option<CollectionType> { None }
|
||||||
|
|
||||||
|
fn total_items(&self) -> Option<u64> { None }
|
||||||
|
fn current(&self) -> Node<Self::CollectionPage> { Node::Empty }
|
||||||
|
fn first(&self) -> Node<Self::CollectionPage> { Node::Empty }
|
||||||
|
fn last(&self) -> Node<Self::CollectionPage> { Node::Empty }
|
||||||
|
fn items(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn ordered_items(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CollectionMut : ObjectMut {
|
||||||
|
type CollectionPage : CollectionPage;
|
||||||
|
|
||||||
|
fn set_collection_type(self, val: Option<CollectionType>) -> Self;
|
||||||
|
fn set_total_items(self, val: Option<u64>) -> Self;
|
||||||
|
fn set_current(self, val: Node<Self::CollectionPage>) -> Self;
|
||||||
|
fn set_first(self, val: Node<Self::CollectionPage>) -> Self;
|
||||||
|
fn set_last(self, val: Node<Self::CollectionPage>) -> Self;
|
||||||
|
fn set_items(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_ordered_items(self, val: Node<Self::Object>) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Collection for serde_json::Value {
|
||||||
|
type CollectionPage = serde_json::Value;
|
||||||
|
|
||||||
|
getter! { collection_type -> type CollectionType }
|
||||||
|
getter! { total_items::totalItems -> u64 }
|
||||||
|
getter! { current -> node Self::CollectionPage }
|
||||||
|
getter! { first -> node Self::CollectionPage }
|
||||||
|
getter! { last -> node Self::CollectionPage }
|
||||||
|
getter! { items -> node <Self as Object>::Object }
|
||||||
|
getter! { ordered_items::orderedItems -> node <Self as Object>::Object }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CollectionMut for serde_json::Value {
|
||||||
|
type CollectionPage = serde_json::Value;
|
||||||
|
|
||||||
|
setter! { collection_type -> type CollectionType }
|
||||||
|
setter! { total_items::totalItems -> u64 }
|
||||||
|
setter! { current -> node Self::CollectionPage }
|
||||||
|
setter! { first -> node Self::CollectionPage }
|
||||||
|
setter! { last -> node Self::CollectionPage }
|
||||||
|
setter! { items -> node <Self as Object>::Object }
|
||||||
|
setter! { ordered_items::orderedItems -> node <Self as Object>::Object }
|
||||||
|
}
|
25
apb/src/object/collection/page.rs
Normal file
25
apb/src/object/collection/page.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::{Node, getter, setter};
|
||||||
|
|
||||||
|
pub trait CollectionPage : super::Collection {
|
||||||
|
fn part_of(&self) -> Node<Self::Collection> { Node::Empty }
|
||||||
|
fn next(&self) -> Node<Self::CollectionPage> { Node::Empty }
|
||||||
|
fn prev(&self) -> Node<Self::CollectionPage> { Node::Empty }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CollectionPageMut : super::CollectionMut {
|
||||||
|
fn set_part_of(self, val: Node<Self::Collection>) -> Self;
|
||||||
|
fn set_next(self, val: Node<Self::CollectionPage>) -> Self;
|
||||||
|
fn set_prev(self, val: Node<Self::CollectionPage>) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CollectionPage for serde_json::Value {
|
||||||
|
getter! { part_of::partOf -> node Self::Collection }
|
||||||
|
getter! { next -> node Self::CollectionPage }
|
||||||
|
getter! { prev -> node Self::CollectionPage }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CollectionPageMut for serde_json::Value {
|
||||||
|
setter! { part_of::partOf -> node Self::Collection }
|
||||||
|
setter! { next -> node Self::CollectionPage }
|
||||||
|
setter! { prev -> node Self::CollectionPage }
|
||||||
|
}
|
|
@ -26,8 +26,3 @@ impl Document for serde_json::Value {
|
||||||
impl DocumentMut for serde_json::Value {
|
impl DocumentMut for serde_json::Value {
|
||||||
setter! { document_type -> type DocumentType }
|
setter! { document_type -> type DocumentType }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub trait Image : Document {}
|
|
||||||
impl Image for serde_json::Value {}
|
|
202
apb/src/object/mod.rs
Normal file
202
apb/src/object/mod.rs
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
pub mod activity;
|
||||||
|
pub mod actor;
|
||||||
|
pub mod collection;
|
||||||
|
pub mod document;
|
||||||
|
pub mod tombstone;
|
||||||
|
pub mod place;
|
||||||
|
pub mod profile;
|
||||||
|
pub mod relationship;
|
||||||
|
|
||||||
|
use crate::{getter, setter, strenum};
|
||||||
|
|
||||||
|
use super::{Base, BaseMut, Link, Node};
|
||||||
|
|
||||||
|
use actor::{Actor, ActorType};
|
||||||
|
use document::{Document, DocumentType};
|
||||||
|
use activity::ActivityType;
|
||||||
|
use collection::{Collection, CollectionType};
|
||||||
|
|
||||||
|
strenum! {
|
||||||
|
pub enum ObjectType {
|
||||||
|
Object,
|
||||||
|
Article,
|
||||||
|
Event,
|
||||||
|
Note,
|
||||||
|
Place,
|
||||||
|
Profile,
|
||||||
|
Relationship,
|
||||||
|
Tombstone;
|
||||||
|
|
||||||
|
Activity(ActivityType),
|
||||||
|
Actor(ActorType),
|
||||||
|
Collection(CollectionType),
|
||||||
|
Document(DocumentType)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Object : Base {
|
||||||
|
type Link : Link;
|
||||||
|
type Actor : Actor;
|
||||||
|
type Object : Object;
|
||||||
|
type Collection : Collection;
|
||||||
|
type Document : Document;
|
||||||
|
|
||||||
|
fn object_type(&self) -> Option<ObjectType> { None }
|
||||||
|
fn attachment(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn attributed_to(&self) -> Node<Self::Actor> { Node::Empty }
|
||||||
|
fn audience(&self) -> Node<Self::Actor> { Node::Empty }
|
||||||
|
fn content(&self) -> Option<&str> { None } // TODO handle language maps
|
||||||
|
fn context(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn name(&self) -> Option<&str> { None } // also in link // TODO handle language maps
|
||||||
|
fn end_time(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
||||||
|
fn generator(&self) -> Node<Self::Actor> { Node::Empty }
|
||||||
|
fn icon(&self) -> Node<Self::Document> { Node::Empty }
|
||||||
|
fn image(&self) -> Node<Self::Document> { Node::Empty }
|
||||||
|
fn in_reply_to(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn location(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn preview(&self) -> Node<Self::Object> { Node::Empty } // also in link
|
||||||
|
fn published(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
||||||
|
fn replies(&self) -> Node<Self::Collection> { Node::Empty }
|
||||||
|
fn start_time(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
||||||
|
fn summary(&self) -> Option<&str> { None }
|
||||||
|
fn tag(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
|
fn updated(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
||||||
|
fn url(&self) -> Node<Self::Link> { Node::Empty }
|
||||||
|
fn to(&self) -> Node<Self::Link> { Node::Empty }
|
||||||
|
fn bto(&self) -> Node<Self::Link> { Node::Empty }
|
||||||
|
fn cc(&self) -> Node<Self::Link> { Node::Empty }
|
||||||
|
fn bcc(&self) -> Node<Self::Link> { Node::Empty }
|
||||||
|
fn media_type(&self) -> Option<&str> { None } // also in link
|
||||||
|
fn duration(&self) -> Option<&str> { None } // TODO how to parse xsd:duration ?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ObjectMut : BaseMut {
|
||||||
|
type Link : Link;
|
||||||
|
type Actor : Actor;
|
||||||
|
type Object : Object;
|
||||||
|
type Collection : Collection;
|
||||||
|
type Document : Document;
|
||||||
|
|
||||||
|
fn set_object_type(self, val: Option<ObjectType>) -> Self;
|
||||||
|
fn set_attachment(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_attributed_to(self, val: Node<Self::Actor>) -> Self;
|
||||||
|
fn set_audience(self, val: Node<Self::Actor>) -> Self;
|
||||||
|
fn set_content(self, val: Option<&str>) -> Self; // TODO handle language maps
|
||||||
|
fn set_context(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_name(self, val: Option<&str>) -> Self; // also in link // TODO handle language maps
|
||||||
|
fn set_end_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
||||||
|
fn set_generator(self, val: Node<Self::Actor>) -> Self;
|
||||||
|
fn set_icon(self, val: Node<Self::Document>) -> Self;
|
||||||
|
fn set_image(self, val: Node<Self::Document>) -> Self;
|
||||||
|
fn set_in_reply_to(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_location(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_preview(self, val: Node<Self::Object>) -> Self; // also in link
|
||||||
|
fn set_published(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
||||||
|
fn set_replies(self, val: Node<Self::Collection>) -> Self;
|
||||||
|
fn set_start_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
||||||
|
fn set_summary(self, val: Option<&str>) -> Self;
|
||||||
|
fn set_tag(self, val: Node<Self::Object>) -> Self;
|
||||||
|
fn set_updated(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
||||||
|
fn set_url(self, val: Node<Self::Link>) -> Self;
|
||||||
|
fn set_to(self, val: Node<Self::Link>) -> Self;
|
||||||
|
fn set_bto(self, val: Node<Self::Link>) -> Self;
|
||||||
|
fn set_cc(self, val: Node<Self::Link>) -> Self;
|
||||||
|
fn set_bcc(self, val: Node<Self::Link>) -> Self;
|
||||||
|
fn set_media_type(self, val: Option<&str>) -> Self; // also in link
|
||||||
|
fn set_duration(self, val: Option<&str>) -> Self; // TODO how to parse xsd:duration ?
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for serde_json::Value {
|
||||||
|
type Link = serde_json::Value;
|
||||||
|
type Actor = serde_json::Value;
|
||||||
|
type Object = serde_json::Value;
|
||||||
|
type Document = serde_json::Value;
|
||||||
|
type Collection = serde_json::Value;
|
||||||
|
|
||||||
|
getter! { object_type -> type ObjectType }
|
||||||
|
getter! { attachment -> node <Self as Object>::Object }
|
||||||
|
getter! { attributed_to::attributedTo -> node Self::Actor }
|
||||||
|
getter! { audience -> node Self::Actor }
|
||||||
|
getter! { content -> &str }
|
||||||
|
getter! { name -> &str }
|
||||||
|
getter! { end_time::endTime -> chrono::DateTime<chrono::Utc> }
|
||||||
|
getter! { generator -> node Self::Actor }
|
||||||
|
getter! { icon -> node Self::Document }
|
||||||
|
getter! { image -> node Self::Document }
|
||||||
|
getter! { in_reply_to::inReplyTo -> node <Self as Object>::Object }
|
||||||
|
getter! { location -> node <Self as Object>::Object }
|
||||||
|
getter! { preview -> node <Self as Object>::Object }
|
||||||
|
getter! { published -> chrono::DateTime<chrono::Utc> }
|
||||||
|
getter! { replies -> node Self::Collection }
|
||||||
|
getter! { start_time::startTime -> chrono::DateTime<chrono::Utc> }
|
||||||
|
getter! { summary -> &str }
|
||||||
|
getter! { tag -> node <Self as Object>::Object }
|
||||||
|
getter! { updated -> chrono::DateTime<chrono::Utc> }
|
||||||
|
getter! { to -> node Self::Link }
|
||||||
|
getter! { bto -> node Self::Link }
|
||||||
|
getter! { cc -> node Self::Link }
|
||||||
|
getter! { bcc -> node Self::Link }
|
||||||
|
getter! { media_type -> &str }
|
||||||
|
getter! { duration -> &str }
|
||||||
|
getter! { url -> node Self::Link }
|
||||||
|
|
||||||
|
// TODO Mastodon doesn't use a "context" field on the object but makes up a new one!!
|
||||||
|
fn context(&self) -> Node<<Self as Object>::Object> {
|
||||||
|
match self.get("context") {
|
||||||
|
Some(x) => Node::from(x.clone()),
|
||||||
|
None => match self.get("conversation") {
|
||||||
|
Some(x) => Node::from(x.clone()),
|
||||||
|
None => Node::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectMut for serde_json::Value {
|
||||||
|
type Link = serde_json::Value;
|
||||||
|
type Actor = serde_json::Value;
|
||||||
|
type Object = serde_json::Value;
|
||||||
|
type Document = serde_json::Value;
|
||||||
|
type Collection = serde_json::Value;
|
||||||
|
|
||||||
|
setter! { object_type -> type ObjectType }
|
||||||
|
setter! { attachment -> node <Self as Object>::Object }
|
||||||
|
setter! { attributed_to::attributedTo -> node Self::Actor }
|
||||||
|
setter! { audience -> node Self::Actor }
|
||||||
|
setter! { content -> &str }
|
||||||
|
setter! { name -> &str }
|
||||||
|
setter! { end_time::endTime -> chrono::DateTime<chrono::Utc> }
|
||||||
|
setter! { generator -> node Self::Actor }
|
||||||
|
setter! { icon -> node Self::Document }
|
||||||
|
setter! { image -> node Self::Document }
|
||||||
|
setter! { in_reply_to::inReplyTo -> node <Self as Object>::Object }
|
||||||
|
setter! { location -> node <Self as Object>::Object }
|
||||||
|
setter! { preview -> node <Self as Object>::Object }
|
||||||
|
setter! { published -> chrono::DateTime<chrono::Utc> }
|
||||||
|
setter! { replies -> node Self::Collection }
|
||||||
|
setter! { start_time::startTime -> chrono::DateTime<chrono::Utc> }
|
||||||
|
setter! { summary -> &str }
|
||||||
|
setter! { tag -> node <Self as Object>::Object }
|
||||||
|
setter! { updated -> chrono::DateTime<chrono::Utc> }
|
||||||
|
setter! { to -> node Self::Link }
|
||||||
|
setter! { bto -> node Self::Link}
|
||||||
|
setter! { cc -> node Self::Link }
|
||||||
|
setter! { bcc -> node Self::Link }
|
||||||
|
setter! { media_type -> &str }
|
||||||
|
setter! { duration -> &str }
|
||||||
|
setter! { url -> node Self::Link }
|
||||||
|
|
||||||
|
// TODO Mastodon doesn't use a "context" field on the object but makes up a new one!!
|
||||||
|
fn set_context(mut self, ctx: Node<<Self as Object>::Object>) -> Self {
|
||||||
|
if let Some(conversation) = ctx.id() {
|
||||||
|
crate::macros::set_maybe_value(
|
||||||
|
&mut self, "conversation", Some(serde_json::Value::String(conversation)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
crate::macros::set_maybe_node(
|
||||||
|
&mut self, "context", ctx
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
use crate::activitystream::Node;
|
use crate::Node;
|
||||||
|
|
||||||
pub trait Relationship : super::Object {
|
pub trait Relationship : super::Object {
|
||||||
fn subject(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
|
fn subject(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
fn relationship(&self) -> Option<&str> { None } // TODO what does this mean???
|
fn relationship(&self) -> Option<&str> { None } // TODO what does this mean???
|
||||||
// TODO was just object but clashes with Activity
|
// TODO was just object but clashes with Activity
|
||||||
fn relationship_object(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
|
fn relationship_object(&self) -> Node<Self::Object> { Node::Empty }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RelationshipMut : super::ObjectMut {
|
pub trait RelationshipMut : super::ObjectMut {
|
||||||
fn set_subject(self, val: Node<impl super::Object>) -> Self;
|
fn set_subject(self, val: Node<Self::Object>) -> Self;
|
||||||
fn set_relationship(self, val: Option<&str>) -> Self; // TODO what does this mean???
|
fn set_relationship(self, val: Option<&str>) -> Self; // TODO what does this mean???
|
||||||
// TODO was just object but clashes with Activity
|
// TODO was just object but clashes with Activity
|
||||||
fn set_relationship_object(self, val: Node<impl super::Object>) -> Self;
|
fn set_relationship_object(self, val: Node<Self::Object>) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Relationship for serde_json::Value {
|
impl Relationship for serde_json::Value {
|
|
@ -1,10 +1,10 @@
|
||||||
pub trait Tombstone : super::Object {
|
pub trait Tombstone : super::Object {
|
||||||
fn former_type(&self) -> Option<super::super::BaseType> { None }
|
fn former_type(&self) -> Option<crate::BaseType> { None }
|
||||||
fn deleted(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
fn deleted(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TombstoneMut : super::ObjectMut {
|
pub trait TombstoneMut : super::ObjectMut {
|
||||||
fn set_former_type(self, val: Option<super::super::BaseType>) -> Self;
|
fn set_former_type(self, val: Option<crate::BaseType>) -> Self;
|
||||||
fn set_deleted(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
fn set_deleted(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use axum::{extract::{Path, State}, http::StatusCode};
|
use axum::{extract::{Path, State}, http::StatusCode};
|
||||||
use sea_orm::EntityTrait;
|
use sea_orm::EntityTrait;
|
||||||
use crate::{activitystream::{object::{activity::ActivityMut, ObjectMut}, BaseMut, Node}, model::{self, activity, object}, server::Context};
|
use crate::{model::{self, activity, object}, server::Context};
|
||||||
|
use apb::{ActivityMut, ObjectMut, BaseMut, Node};
|
||||||
|
|
||||||
use super::{jsonld::LD, JsonLD};
|
use super::{jsonld::LD, JsonLD};
|
||||||
|
|
||||||
|
@ -13,9 +14,9 @@ pub fn ap_activity(activity: model::activity::Model) -> serde_json::Value {
|
||||||
.set_target(Node::maybe_link(activity.target))
|
.set_target(Node::maybe_link(activity.target))
|
||||||
.set_published(Some(activity.published))
|
.set_published(Some(activity.published))
|
||||||
.set_to(Node::links(activity.to.0.clone()))
|
.set_to(Node::links(activity.to.0.clone()))
|
||||||
.set_bto(Node::empty())
|
.set_bto(Node::Empty)
|
||||||
.set_cc(Node::links(activity.cc.0.clone()))
|
.set_cc(Node::links(activity.cc.0.clone()))
|
||||||
.set_bcc(Node::empty())
|
.set_bcc(Node::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use axum::{extract::{Query, State}, http::StatusCode};
|
use axum::{extract::{Query, State}, http::StatusCode};
|
||||||
use sea_orm::{ColumnTrait, Condition, EntityTrait, Order, QueryFilter, QueryOrder, QuerySelect};
|
use sea_orm::{ColumnTrait, Condition, EntityTrait, Order, QueryFilter, QueryOrder, QuerySelect};
|
||||||
|
|
||||||
use crate::{activitystream::Node, auth::{AuthIdentity, Identity}, errors::UpubError, model, server::Context, url};
|
use crate::{auth::{AuthIdentity, Identity}, errors::UpubError, model, server::Context, url};
|
||||||
|
|
||||||
use super::{activity::ap_activity, jsonld::LD, JsonLD, Pagination, PUBLIC_TARGET};
|
use super::{activity::ap_activity, jsonld::LD, JsonLD, Pagination, PUBLIC_TARGET};
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ pub async fn page(
|
||||||
offset, limit,
|
offset, limit,
|
||||||
activities
|
activities
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(_, a)| Some(Node::object(ap_activity(a?))))
|
.filter_map(|(_, a)| Some(ap_activity(a?)))
|
||||||
.collect::<Vec<Node<serde_json::Value>>>()
|
.collect::<Vec<serde_json::Value>>()
|
||||||
).ld_context()
|
).ld_context()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,25 @@ use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter};
|
||||||
|
|
||||||
use crate::{activitystream::{key::PublicKeyMut, object::{actor::{ActorMut, ActorType}, ObjectMut}, BaseMut, Node}, model, server::Context, url};
|
use apb::{PublicKeyMut, ActorMut, ActorType, Link, Object, ObjectMut, BaseMut, Node};
|
||||||
|
use crate::{model, server::Context, url};
|
||||||
|
|
||||||
use self::jsonld::LD;
|
use self::jsonld::LD;
|
||||||
|
|
||||||
|
pub trait Addressed : Object {
|
||||||
|
fn addressed(&self) -> Vec<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Addressed for serde_json::Value {
|
||||||
|
fn addressed(&self) -> Vec<String> {
|
||||||
|
let mut to : Vec<String> = self.to().map(|x| x.href().to_string()).collect();
|
||||||
|
to.append(&mut self.bto().map(|x| x.href().to_string()).collect());
|
||||||
|
to.append(&mut self.cc().map(|x| x.href().to_string()).collect());
|
||||||
|
to.append(&mut self.bcc().map(|x| x.href().to_string()).collect());
|
||||||
|
to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const PUBLIC_TARGET : &str = "https://www.w3.org/ns/activitystreams#Public";
|
pub const PUBLIC_TARGET : &str = "https://www.w3.org/ns/activitystreams#Public";
|
||||||
|
|
||||||
pub fn split_id(id: &str) -> (String, String) {
|
pub fn split_id(id: &str) -> (String, String) {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use axum::{extract::{Path, State}, http::StatusCode};
|
use axum::{extract::{Path, State}, http::StatusCode};
|
||||||
use sea_orm::EntityTrait;
|
use sea_orm::EntityTrait;
|
||||||
|
|
||||||
use crate::{activitystream::{object::ObjectMut, BaseMut, Node}, model::{self, object}, server::Context};
|
use apb::{ObjectMut, BaseMut, Node};
|
||||||
|
use crate::{model::{self, object}, server::Context};
|
||||||
|
|
||||||
use super::{jsonld::LD, JsonLD};
|
use super::{jsonld::LD, JsonLD};
|
||||||
|
|
||||||
|
@ -16,9 +17,9 @@ pub fn ap_object(object: model::object::Model) -> serde_json::Value {
|
||||||
.set_context(Node::maybe_link(object.context.clone()))
|
.set_context(Node::maybe_link(object.context.clone()))
|
||||||
.set_published(Some(object.published))
|
.set_published(Some(object.published))
|
||||||
.set_to(Node::links(object.to.0.clone()))
|
.set_to(Node::links(object.to.0.clone()))
|
||||||
.set_bto(Node::empty())
|
.set_bto(Node::Empty)
|
||||||
.set_cc(Node::links(object.cc.0.clone()))
|
.set_cc(Node::links(object.cc.0.clone()))
|
||||||
.set_bcc(Node::empty())
|
.set_bcc(Node::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use axum::{extract::{Path, Query, State}, http::StatusCode};
|
use axum::{extract::{Path, Query, State}, http::StatusCode};
|
||||||
use sea_orm::{ColumnTrait, Condition, EntityTrait, PaginatorTrait, QueryFilter, QuerySelect, SelectColumns};
|
use sea_orm::{ColumnTrait, Condition, EntityTrait, PaginatorTrait, QueryFilter, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::{activitypub::{jsonld::LD, JsonLD, Pagination}, activitystream::Node, model, server::Context, url};
|
use crate::{activitypub::{jsonld::LD, JsonLD, Pagination}, model, server::Context, url};
|
||||||
|
|
||||||
use model::relation::Column::{Following, Follower};
|
use model::relation::Column::{Following, Follower};
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ pub async fn page<const OUTGOING: bool>(
|
||||||
&url!(ctx, "/users/{id}/{follow___}"),
|
&url!(ctx, "/users/{id}/{follow___}"),
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
following.into_iter().map(Node::link).collect()
|
following.into_iter().map(serde_json::Value::String).collect()
|
||||||
).ld_context()
|
).ld_context()
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
||||||
use sea_orm::{sea_query::Expr, ColumnTrait, Condition, EntityTrait, IntoActiveModel, Order, QueryFilter, QueryOrder, QuerySelect, Set};
|
use sea_orm::{sea_query::Expr, ColumnTrait, Condition, EntityTrait, IntoActiveModel, Order, QueryFilter, QueryOrder, QuerySelect, Set};
|
||||||
|
|
||||||
use crate::{activitypub::{activity::ap_activity, jsonld::LD, JsonLD, Pagination}, activitystream::{object::{activity::{Activity, ActivityType}, Addressed, Object, ObjectType}, Base, BaseType, Node}, auth::{AuthIdentity, Identity}, errors::{LoggableError, UpubError}, model, server::Context, url};
|
use apb::{Activity, ActivityType, Object, ObjectType, Base, BaseType};
|
||||||
|
use crate::{activitypub::{activity::ap_activity, jsonld::LD, Addressed, JsonLD, Pagination}, auth::{AuthIdentity, Identity}, errors::{LoggableError, UpubError}, model, server::Context, url};
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
|
@ -48,8 +49,8 @@ pub async fn page(
|
||||||
offset, limit,
|
offset, limit,
|
||||||
activities
|
activities
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(_, a)| Some(Node::object(ap_activity(a?))))
|
.filter_map(|(_, a)| Some(ap_activity(a?)))
|
||||||
.collect::<Vec<Node<serde_json::Value>>>()
|
.collect::<Vec<serde_json::Value>>()
|
||||||
).ld_context()
|
).ld_context()
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
|
@ -84,7 +85,7 @@ pub async fn post(
|
||||||
|
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Delete))) => {
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Delete))) => {
|
||||||
// TODO verify the signature before just deleting lmao
|
// TODO verify the signature before just deleting lmao
|
||||||
let oid = object.object().id().ok_or(StatusCode::BAD_REQUEST)?.to_string();
|
let oid = object.object().id().ok_or(StatusCode::BAD_REQUEST)?;
|
||||||
// TODO maybe we should keep the tombstone?
|
// TODO maybe we should keep the tombstone?
|
||||||
model::user::Entity::delete_by_id(&oid).exec(ctx.db()).await.info_failed("failed deleting from users");
|
model::user::Entity::delete_by_id(&oid).exec(ctx.db()).await.info_failed("failed deleting from users");
|
||||||
model::activity::Entity::delete_by_id(&oid).exec(ctx.db()).await.info_failed("failed deleting from activities");
|
model::activity::Entity::delete_by_id(&oid).exec(ctx.db()).await.info_failed("failed deleting from activities");
|
||||||
|
@ -152,8 +153,8 @@ pub async fn post(
|
||||||
},
|
},
|
||||||
|
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => {
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => {
|
||||||
let aid = object.actor().id().ok_or(StatusCode::BAD_REQUEST)?.to_string();
|
let aid = object.actor().id().ok_or(StatusCode::BAD_REQUEST)?;
|
||||||
let oid = object.object().id().ok_or(StatusCode::BAD_REQUEST)?.to_string();
|
let oid = object.object().id().ok_or(StatusCode::BAD_REQUEST)?;
|
||||||
let like = model::like::ActiveModel {
|
let like = model::like::ActiveModel {
|
||||||
id: sea_orm::ActiveValue::NotSet,
|
id: sea_orm::ActiveValue::NotSet,
|
||||||
actor: sea_orm::Set(aid.clone()),
|
actor: sea_orm::Set(aid.clone()),
|
||||||
|
@ -182,7 +183,7 @@ pub async fn post(
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) => {
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) => {
|
||||||
let activity_model = model::activity::Model::new(&object)?;
|
let activity_model = model::activity::Model::new(&object)?;
|
||||||
let activity_targets = object.addressed();
|
let activity_targets = object.addressed();
|
||||||
let Some(object_node) = object.object().get() else {
|
let Some(object_node) = object.object().extract() else {
|
||||||
// TODO we could process non-embedded activities or arrays but im lazy rn
|
// TODO we could process non-embedded activities or arrays but im lazy rn
|
||||||
tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&object).unwrap());
|
tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&object).unwrap());
|
||||||
return Err(StatusCode::UNPROCESSABLE_ENTITY.into());
|
return Err(StatusCode::UNPROCESSABLE_ENTITY.into());
|
||||||
|
@ -200,7 +201,7 @@ pub async fn post(
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Update))) => {
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Update))) => {
|
||||||
let activity_model = model::activity::Model::new(&object)?;
|
let activity_model = model::activity::Model::new(&object)?;
|
||||||
let activity_targets = object.addressed();
|
let activity_targets = object.addressed();
|
||||||
let Some(object_node) = object.object().get() else {
|
let Some(object_node) = object.object().extract() else {
|
||||||
// TODO we could process non-embedded activities or arrays but im lazy rn
|
// TODO we could process non-embedded activities or arrays but im lazy rn
|
||||||
tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&object).unwrap());
|
tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&object).unwrap());
|
||||||
return Err(StatusCode::UNPROCESSABLE_ENTITY.into());
|
return Err(StatusCode::UNPROCESSABLE_ENTITY.into());
|
||||||
|
@ -214,7 +215,7 @@ pub async fn post(
|
||||||
Some(ObjectType::Actor(_)) => {
|
Some(ObjectType::Actor(_)) => {
|
||||||
// TODO oof here is an example of the weakness of this model, we have to go all the way
|
// TODO oof here is an example of the weakness of this model, we have to go all the way
|
||||||
// back up to serde_json::Value because impl Object != impl Actor
|
// back up to serde_json::Value because impl Object != impl Actor
|
||||||
let actor_model = model::user::Model::new(&object_node.underlying_json_object())?;
|
let actor_model = model::user::Model::new(&object_node)?;
|
||||||
model::user::Entity::update(actor_model.into_active_model())
|
model::user::Entity::update(actor_model.into_active_model())
|
||||||
.exec(ctx.db()).await?;
|
.exec(ctx.db()).await?;
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,8 @@ pub mod following;
|
||||||
use axum::{extract::{Path, State}, http::StatusCode};
|
use axum::{extract::{Path, State}, http::StatusCode};
|
||||||
use sea_orm::EntityTrait;
|
use sea_orm::EntityTrait;
|
||||||
|
|
||||||
use crate::{activitystream::{key::PublicKeyMut, object::{actor::ActorMut, document::{DocumentMut, DocumentType}, ObjectMut}, BaseMut, Node}, model::{self, user}, server::Context, url};
|
use apb::{PublicKeyMut, ActorMut, DocumentMut, DocumentType, ObjectMut, BaseMut, Node};
|
||||||
|
use crate::{model::{self, user}, server::Context, url};
|
||||||
|
|
||||||
use super::{jsonld::LD, JsonLD};
|
use super::{jsonld::LD, JsonLD};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
||||||
use sea_orm::{EntityTrait, IntoActiveModel, Order, QueryOrder, QuerySelect, Set};
|
use sea_orm::{EntityTrait, IntoActiveModel, Order, QueryOrder, QuerySelect, Set};
|
||||||
|
|
||||||
use crate::{activitypub::{jsonld::LD, CreationResult, JsonLD, Pagination}, activitystream::{object::{activity::{accept::AcceptType, Activity, ActivityMut, ActivityType}, Addressed, ObjectMut}, Base, BaseMut, BaseType, Node, ObjectType}, auth::{AuthIdentity, Identity}, errors::UpubError, model, server::Context, url};
|
use apb::{AcceptType, Activity, ActivityMut, ActivityType, ObjectMut, Base, BaseMut, BaseType, Node, ObjectType};
|
||||||
|
use crate::{activitypub::{jsonld::LD, Addressed, CreationResult, JsonLD, Pagination}, auth::{AuthIdentity, Identity}, errors::UpubError, model, server::Context, url};
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
|
@ -49,13 +50,11 @@ pub async fn page(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(a, o)| {
|
.map(|(a, o)| {
|
||||||
let oid = a.object.clone();
|
let oid = a.object.clone();
|
||||||
Node::object(
|
super::super::activity::ap_activity(a)
|
||||||
super::super::activity::ap_activity(a)
|
.set_object(match o {
|
||||||
.set_object(match o {
|
Some(o) => Node::object(super::super::object::ap_object(o)),
|
||||||
Some(o) => Node::object(super::super::object::ap_object(o)),
|
None => Node::maybe_link(oid),
|
||||||
None => Node::maybe_link(oid),
|
})
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
).ld_context()
|
).ld_context()
|
||||||
|
@ -114,7 +113,7 @@ pub async fn post(
|
||||||
},
|
},
|
||||||
|
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) => {
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) => {
|
||||||
let Some(object) = activity.object().get().map(|x| x.underlying_json_object()) else {
|
let Some(object) = activity.object().extract() else {
|
||||||
return Err(StatusCode::BAD_REQUEST.into());
|
return Err(StatusCode::BAD_REQUEST.into());
|
||||||
};
|
};
|
||||||
let oid = ctx.oid(uuid::Uuid::new_v4().to_string());
|
let oid = ctx.oid(uuid::Uuid::new_v4().to_string());
|
||||||
|
@ -152,7 +151,7 @@ pub async fn post(
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => {
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => {
|
||||||
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
||||||
let activity_targets = activity.addressed();
|
let activity_targets = activity.addressed();
|
||||||
let Some(oid) = activity.object().id().map(|x| x.to_string()) else {
|
let Some(oid) = activity.object().id() else {
|
||||||
return Err(StatusCode::BAD_REQUEST.into());
|
return Err(StatusCode::BAD_REQUEST.into());
|
||||||
};
|
};
|
||||||
let activity_model = model::activity::Model::new(
|
let activity_model = model::activity::Model::new(
|
||||||
|
@ -164,7 +163,7 @@ pub async fn post(
|
||||||
|
|
||||||
let like_model = model::like::ActiveModel {
|
let like_model = model::like::ActiveModel {
|
||||||
actor: Set(uid.clone()),
|
actor: Set(uid.clone()),
|
||||||
likes: Set(oid.clone()),
|
likes: Set(oid),
|
||||||
date: Set(chrono::Utc::now()),
|
date: Set(chrono::Utc::now()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
pub mod link;
|
|
||||||
pub use link::{Link, LinkType};
|
|
||||||
|
|
||||||
pub mod object;
|
|
||||||
pub use object::{Object, ObjectType};
|
|
||||||
|
|
||||||
pub mod node;
|
|
||||||
pub use node::Node;
|
|
||||||
|
|
||||||
pub mod macros;
|
|
||||||
pub mod prelude;
|
|
||||||
|
|
||||||
pub mod key;
|
|
||||||
|
|
||||||
use crate::{getter, setter, strenum};
|
|
||||||
|
|
||||||
strenum! {
|
|
||||||
pub enum BaseType {
|
|
||||||
;
|
|
||||||
Object(ObjectType),
|
|
||||||
Link(LinkType)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Base {
|
|
||||||
fn id(&self) -> Option<&str> { None }
|
|
||||||
fn base_type(&self) -> Option<BaseType> { None }
|
|
||||||
|
|
||||||
// TODO this is a dirty fix because my trait model is flawed and leads to circular resolution
|
|
||||||
// errors, basically can't downcast back to serde_json::Value once i've updasted it to
|
|
||||||
// impl Object/Actor/whatever... ask me to infodump+bikeshed about this!!! :3
|
|
||||||
fn underlying_json_object(self) -> serde_json::Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub trait BaseMut {
|
|
||||||
fn set_id(self, val: Option<&str>) -> Self;
|
|
||||||
fn set_base_type(self, val: Option<BaseType>) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Base for String {
|
|
||||||
fn id(&self) -> Option<&str> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn base_type(&self) -> Option<BaseType> {
|
|
||||||
Some(BaseType::Link(LinkType::Link))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn underlying_json_object(self) -> serde_json::Value {
|
|
||||||
serde_json::Value::String(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Base for serde_json::Value {
|
|
||||||
fn underlying_json_object(self) -> serde_json::Value {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
getter! { id -> &str }
|
|
||||||
getter! { base_type -> type BaseType }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BaseMut for serde_json::Value {
|
|
||||||
setter! { id -> &str }
|
|
||||||
setter! { base_type -> type BaseType }
|
|
||||||
}
|
|
|
@ -1,192 +0,0 @@
|
||||||
pub enum Node<T : super::Base> {
|
|
||||||
Array(Vec<Node<T>>), // TODO would be cool to make it Box<[Node<T>]> so that Node is just a ptr
|
|
||||||
Object(Box<T>),
|
|
||||||
Link(Box<dyn super::Link>),
|
|
||||||
Empty,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T : super::Base> From<Option<T>> for Node<T> {
|
|
||||||
fn from(value: Option<T>) -> Self {
|
|
||||||
match value {
|
|
||||||
Some(x) => Node::Object(Box::new(x)),
|
|
||||||
None => Node::Empty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T : super::Base> Node<T> {
|
|
||||||
pub fn get(self) -> Option<T> {
|
|
||||||
match self {
|
|
||||||
Node::Empty | Node::Link(_) => None,
|
|
||||||
Node::Object(x) => Some(*x),
|
|
||||||
Node::Array(v) => v.into_iter().find_map(|x| match x {
|
|
||||||
Node::Object(x) => Some(*x),
|
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO extremely unforgiving, is this even useful?
|
|
||||||
pub fn get_items(&self) -> Option<Vec<&T>> {
|
|
||||||
match self {
|
|
||||||
Node::Empty | Node::Link(_) => None,
|
|
||||||
Node::Object(x) => Some(vec![x]),
|
|
||||||
Node::Array(v) =>
|
|
||||||
Some(v.iter().filter_map(|x| match x {
|
|
||||||
Node::Object(x) => Some(&**x),
|
|
||||||
_ => None,
|
|
||||||
}).collect()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_links(&self) -> Vec<String> {
|
|
||||||
match self {
|
|
||||||
Node::Empty => vec![],
|
|
||||||
Node::Link(x) => vec![x.href().to_string()],
|
|
||||||
Node::Object(x) => match x.id() {
|
|
||||||
Some(x) => vec![x.to_string()],
|
|
||||||
None => vec![],
|
|
||||||
},
|
|
||||||
Node::Array(v) =>
|
|
||||||
v.iter().filter_map(|x| match x {
|
|
||||||
Node::Link(x) => Some(x.href().to_string()),
|
|
||||||
Node::Object(x) => x.id().map(|x| x.to_string()),
|
|
||||||
// TODO handle array of arrays maybe?
|
|
||||||
_ => None,
|
|
||||||
}).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Node::Empty | Node::Link(_) => true,
|
|
||||||
Node::Object(_) | Node::Array(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Node::Empty => 0,
|
|
||||||
Node::Link(_) => 0,
|
|
||||||
Node::Object(_) => 1,
|
|
||||||
Node::Array(v) => v.len(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flat(self) -> Vec<serde_json::Value> {
|
|
||||||
match self {
|
|
||||||
Node::Empty => vec![],
|
|
||||||
Node::Link(l) => vec![serde_json::Value::String(l.href().to_string())],
|
|
||||||
Node::Object(x) => vec![x.underlying_json_object()],
|
|
||||||
Node::Array(arr) => {
|
|
||||||
arr
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|node| match node {
|
|
||||||
Node::Empty => None,
|
|
||||||
Node::Link(l) => Some(serde_json::Value::String(l.href().to_string())),
|
|
||||||
Node::Object(o) => Some(o.underlying_json_object()),
|
|
||||||
Node::Array(_) => Some(serde_json::Value::Array(node.flat())),
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Node::Empty => None,
|
|
||||||
Node::Link(uri) => Some(uri.href().to_string()),
|
|
||||||
Node::Object(obj) => obj.id().map(|x| x.to_string()),
|
|
||||||
Node::Array(arr) => arr.first()?.id().map(|x| x.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Node<serde_json::Value>{
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Node::Empty
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn link(uri: String) -> Self {
|
|
||||||
Node::Link(Box::new(uri))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn links(uris: Vec<String>) -> Self {
|
|
||||||
Node::Array(
|
|
||||||
uris
|
|
||||||
.into_iter()
|
|
||||||
.map(Node::link)
|
|
||||||
.collect()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maybe_link(uri: Option<String>) -> Self {
|
|
||||||
match uri {
|
|
||||||
Some(uri) => Node::Link(Box::new(uri)),
|
|
||||||
None => Node::Empty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn object(x: impl super::Base) -> Self {
|
|
||||||
Node::Object(Box::new(x.underlying_json_object()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maybe_object(x: Option<impl super::Base>) -> Self {
|
|
||||||
match x {
|
|
||||||
Some(x) => Node::Object(Box::new(x.underlying_json_object())),
|
|
||||||
None => Node::Empty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn array(x: Vec<impl super::Base>) -> Self {
|
|
||||||
Node::Array(x.into_iter().map(|x| Node::object(x.underlying_json_object())).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch(&mut self) -> reqwest::Result<()> {
|
|
||||||
if let Node::Link(link) = self {
|
|
||||||
*self = reqwest::Client::new()
|
|
||||||
.get(link.href())
|
|
||||||
.header("Accept", "application/json")
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json::<serde_json::Value>()
|
|
||||||
.await?
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Option<&str>> for Node<serde_json::Value> {
|
|
||||||
fn from(value: Option<&str>) -> Self {
|
|
||||||
match value {
|
|
||||||
Some(x) => Node::Link(Box::new(x.to_string())),
|
|
||||||
None => Node::Empty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for Node<serde_json::Value> {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
Node::Link(Box::new(value.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<serde_json::Value> for Node<serde_json::Value> {
|
|
||||||
fn from(value: serde_json::Value) -> Self {
|
|
||||||
match value {
|
|
||||||
serde_json::Value::String(uri) => Node::Link(Box::new(uri)),
|
|
||||||
serde_json::Value::Object(_) => match value.get("href") {
|
|
||||||
None => Node::Object(Box::new(value)),
|
|
||||||
Some(_) => Node::Link(Box::new(value)),
|
|
||||||
},
|
|
||||||
serde_json::Value::Array(arr) => Node::Array(
|
|
||||||
arr
|
|
||||||
.into_iter()
|
|
||||||
.map(Self::from)
|
|
||||||
.collect()
|
|
||||||
),
|
|
||||||
_ => Node::Empty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
pub mod accept;
|
|
||||||
pub mod ignore;
|
|
||||||
pub mod intransitive;
|
|
||||||
pub mod offer;
|
|
||||||
pub mod reject;
|
|
||||||
|
|
||||||
use crate::activitystream::Node;
|
|
||||||
use crate::{getter, setter, strenum};
|
|
||||||
use accept::AcceptType;
|
|
||||||
use reject::RejectType;
|
|
||||||
use offer::OfferType;
|
|
||||||
use intransitive::IntransitiveActivityType;
|
|
||||||
use ignore::IgnoreType;
|
|
||||||
|
|
||||||
strenum! {
|
|
||||||
pub enum ActivityType {
|
|
||||||
Activity,
|
|
||||||
Add,
|
|
||||||
Announce,
|
|
||||||
Create,
|
|
||||||
Delete,
|
|
||||||
Dislike,
|
|
||||||
Flag,
|
|
||||||
Follow,
|
|
||||||
Join,
|
|
||||||
Leave,
|
|
||||||
Like,
|
|
||||||
Listen,
|
|
||||||
Move,
|
|
||||||
Read,
|
|
||||||
Remove,
|
|
||||||
Undo,
|
|
||||||
Update,
|
|
||||||
View;
|
|
||||||
|
|
||||||
IntransitiveActivity(IntransitiveActivityType),
|
|
||||||
Accept(AcceptType),
|
|
||||||
Ignore(IgnoreType),
|
|
||||||
Offer(OfferType),
|
|
||||||
Reject(RejectType)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Activity : super::Object {
|
|
||||||
fn activity_type(&self) -> Option<ActivityType> { None }
|
|
||||||
fn actor(&self) -> Node<impl super::Actor> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn object(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn target(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn result(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn origin(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn instrument(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ActivityMut : super::ObjectMut {
|
|
||||||
fn set_activity_type(self, val: Option<ActivityType>) -> Self;
|
|
||||||
fn set_actor(self, val: Node<impl super::Actor>) -> Self;
|
|
||||||
fn set_object(self, val: Node<impl super::Object>) -> Self;
|
|
||||||
fn set_target(self, val: Node<impl super::Object>) -> Self;
|
|
||||||
fn set_result(self, val: Node<impl super::Object>) -> Self;
|
|
||||||
fn set_origin(self, val: Node<impl super::Object>) -> Self;
|
|
||||||
fn set_instrument(self, val: Node<impl super::Object>) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Activity for serde_json::Value {
|
|
||||||
getter! { activity_type -> type ActivityType }
|
|
||||||
getter! { actor -> node impl super::Actor }
|
|
||||||
getter! { object -> node impl super::Object }
|
|
||||||
getter! { target -> node impl super::Object }
|
|
||||||
getter! { result -> node impl super::Object }
|
|
||||||
getter! { origin -> node impl super::Object }
|
|
||||||
getter! { instrument -> node impl super::Object }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActivityMut for serde_json::Value {
|
|
||||||
setter! { activity_type -> type ActivityType }
|
|
||||||
setter! { actor -> node impl super::Actor }
|
|
||||||
setter! { object -> node impl super::Object }
|
|
||||||
setter! { target -> node impl super::Object }
|
|
||||||
setter! { result -> node impl super::Object }
|
|
||||||
setter! { origin -> node impl super::Object }
|
|
||||||
setter! { instrument -> node impl super::Object }
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
use crate::{activitystream::Node, getter, setter, strenum};
|
|
||||||
|
|
||||||
use super::collection::Collection;
|
|
||||||
use super::super::key::PublicKey;
|
|
||||||
|
|
||||||
strenum! {
|
|
||||||
pub enum ActorType {
|
|
||||||
Application,
|
|
||||||
Group,
|
|
||||||
Organization,
|
|
||||||
Person;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Actor : super::Object {
|
|
||||||
fn actor_type(&self) -> Option<ActorType> { None }
|
|
||||||
fn preferred_username(&self) -> Option<&str> { None }
|
|
||||||
fn inbox(&self) -> Node<impl Collection>;
|
|
||||||
fn outbox(&self) -> Node<impl Collection>;
|
|
||||||
fn following(&self) -> Node<impl Collection> { Node::empty() }
|
|
||||||
fn followers(&self) -> Node<impl Collection> { Node::empty() }
|
|
||||||
fn liked(&self) -> Node<impl Collection> { Node::empty() }
|
|
||||||
fn streams(&self) -> Node<impl Collection> { Node::empty() }
|
|
||||||
fn endpoints(&self) -> Option<serde_json::Map<String, String>> { None }
|
|
||||||
fn public_key(&self) -> Node<impl PublicKey> { Node::empty() }
|
|
||||||
// idk about this? everyone has it but AP doesn't mention it
|
|
||||||
fn discoverable(&self) -> Option<bool> { None }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ActorMut : super::ObjectMut {
|
|
||||||
fn set_actor_type(self, val: Option<ActorType>) -> Self;
|
|
||||||
fn set_preferred_username(self, val: Option<&str>) -> Self;
|
|
||||||
fn set_inbox(self, val: Node<impl Collection>) -> Self;
|
|
||||||
fn set_outbox(self, val: Node<impl Collection>) -> Self;
|
|
||||||
fn set_following(self, val: Node<impl Collection>) -> Self;
|
|
||||||
fn set_followers(self, val: Node<impl Collection>) -> Self;
|
|
||||||
fn set_liked(self, val: Node<impl Collection>) -> Self;
|
|
||||||
fn set_streams(self, val: Node<impl Collection>) -> Self;
|
|
||||||
fn set_endpoints(self, val: Option<serde_json::Map<String, String>>) -> Self;
|
|
||||||
fn set_public_key(self, val: Node<impl PublicKey>) -> Self;
|
|
||||||
fn set_discoverable(self, val: Option<bool>) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for serde_json::Value {
|
|
||||||
getter! { actor_type -> type ActorType }
|
|
||||||
getter! { preferred_username::preferredUsername -> &str }
|
|
||||||
getter! { inbox -> node impl Collection }
|
|
||||||
getter! { outbox -> node impl Collection }
|
|
||||||
getter! { following -> node impl Collection }
|
|
||||||
getter! { followers -> node impl Collection }
|
|
||||||
getter! { liked -> node impl Collection }
|
|
||||||
getter! { streams -> node impl Collection }
|
|
||||||
getter! { public_key::publicKey -> node impl PublicKey }
|
|
||||||
getter! { discoverable -> bool }
|
|
||||||
|
|
||||||
fn endpoints(&self) -> Option<serde_json::Map<String, String>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActorMut for serde_json::Value {
|
|
||||||
setter! { actor_type -> type ActorType }
|
|
||||||
setter! { preferred_username::preferredUsername -> &str }
|
|
||||||
setter! { inbox -> node impl Collection }
|
|
||||||
setter! { outbox -> node impl Collection }
|
|
||||||
setter! { following -> node impl Collection }
|
|
||||||
setter! { followers -> node impl Collection }
|
|
||||||
setter! { liked -> node impl Collection }
|
|
||||||
setter! { streams -> node impl Collection }
|
|
||||||
setter! { public_key::publicKey -> node impl PublicKey }
|
|
||||||
setter! { discoverable -> bool }
|
|
||||||
|
|
||||||
fn set_endpoints(mut self, _val: Option<serde_json::Map<String, String>>) -> Self {
|
|
||||||
self.as_object_mut().unwrap().insert("endpoints".to_string(), serde_json::Value::Object(serde_json::Map::default()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
pub mod page;
|
|
||||||
pub use page::CollectionPage;
|
|
||||||
|
|
||||||
use crate::activitystream::Node;
|
|
||||||
use crate::{getter, setter, strenum};
|
|
||||||
|
|
||||||
strenum! {
|
|
||||||
pub enum CollectionType {
|
|
||||||
Collection,
|
|
||||||
CollectionPage,
|
|
||||||
OrderedCollection,
|
|
||||||
OrderedCollectionPage;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Collection : super::Object {
|
|
||||||
fn collection_type(&self) -> Option<CollectionType> { None }
|
|
||||||
|
|
||||||
fn total_items(&self) -> Option<u64> { None }
|
|
||||||
fn current(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn first(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn last(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn items(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn ordered_items(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CollectionMut : super::ObjectMut {
|
|
||||||
fn set_collection_type(self, val: Option<CollectionType>) -> Self;
|
|
||||||
fn set_total_items(self, val: Option<u64>) -> Self;
|
|
||||||
fn set_current(self, val: Node<impl CollectionPage>) -> Self;
|
|
||||||
fn set_first(self, val: Node<impl CollectionPage>) -> Self;
|
|
||||||
fn set_last(self, val: Node<impl CollectionPage>) -> Self;
|
|
||||||
fn set_items(self, val: Node<impl super::Object>) -> Self;
|
|
||||||
fn set_ordered_items(self, val: Node<impl super::Object>) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Collection for serde_json::Value {
|
|
||||||
getter! { collection_type -> type CollectionType }
|
|
||||||
getter! { total_items::totalItems -> u64 }
|
|
||||||
getter! { current -> node impl CollectionPage }
|
|
||||||
getter! { first -> node impl CollectionPage }
|
|
||||||
getter! { last -> node impl CollectionPage }
|
|
||||||
getter! { items -> node impl super::Object }
|
|
||||||
getter! { ordered_items::orderedItems -> node impl super::Object }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CollectionMut for serde_json::Value {
|
|
||||||
setter! { collection_type -> type CollectionType }
|
|
||||||
setter! { total_items::totalItems -> u64 }
|
|
||||||
setter! { current -> node impl CollectionPage }
|
|
||||||
setter! { first -> node impl CollectionPage }
|
|
||||||
setter! { last -> node impl CollectionPage }
|
|
||||||
setter! { items -> node impl super::Object }
|
|
||||||
setter! { ordered_items::orderedItems -> node impl super::Object }
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
use crate::{activitystream::Node, getter, setter};
|
|
||||||
|
|
||||||
pub trait CollectionPage : super::Collection {
|
|
||||||
fn part_of(&self) -> Node<impl super::Collection> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn next(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn prev(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CollectionPageMut : super::CollectionMut {
|
|
||||||
fn set_part_of(self, val: Node<impl super::Collection>) -> Self;
|
|
||||||
fn set_next(self, val: Node<impl CollectionPage>) -> Self;
|
|
||||||
fn set_prev(self, val: Node<impl CollectionPage>) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CollectionPage for serde_json::Value {
|
|
||||||
getter! { part_of::partOf -> node impl super::Collection }
|
|
||||||
getter! { next -> node impl CollectionPage }
|
|
||||||
getter! { prev -> node impl CollectionPage }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CollectionPageMut for serde_json::Value {
|
|
||||||
setter! { part_of::partOf -> node impl super::Collection }
|
|
||||||
setter! { next -> node impl CollectionPage }
|
|
||||||
setter! { prev -> node impl CollectionPage }
|
|
||||||
}
|
|
|
@ -1,191 +0,0 @@
|
||||||
pub mod activity;
|
|
||||||
pub mod actor;
|
|
||||||
pub mod collection;
|
|
||||||
pub mod document;
|
|
||||||
pub mod tombstone;
|
|
||||||
pub mod place;
|
|
||||||
pub mod profile;
|
|
||||||
pub mod relationship;
|
|
||||||
|
|
||||||
use crate::{getter, setter, strenum};
|
|
||||||
|
|
||||||
use super::{Link, Node};
|
|
||||||
|
|
||||||
use actor::{Actor, ActorType};
|
|
||||||
use document::{Image, DocumentType};
|
|
||||||
use activity::ActivityType;
|
|
||||||
use collection::{Collection, CollectionType};
|
|
||||||
|
|
||||||
strenum! {
|
|
||||||
pub enum ObjectType {
|
|
||||||
Object,
|
|
||||||
Article,
|
|
||||||
Event,
|
|
||||||
Note,
|
|
||||||
Place,
|
|
||||||
Profile,
|
|
||||||
Relationship,
|
|
||||||
Tombstone;
|
|
||||||
|
|
||||||
Activity(ActivityType),
|
|
||||||
Actor(ActorType),
|
|
||||||
Collection(CollectionType),
|
|
||||||
Document(DocumentType)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Object : super::Base {
|
|
||||||
fn object_type(&self) -> Option<ObjectType> { None }
|
|
||||||
fn attachment(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn attributed_to(&self) -> Node<impl Actor> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn audience(&self) -> Node<impl Actor> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn content(&self) -> Option<&str> { None } // TODO handle language maps
|
|
||||||
fn context(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn name(&self) -> Option<&str> { None } // also in link // TODO handle language maps
|
|
||||||
fn end_time(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
|
||||||
fn generator(&self) -> Node<impl Actor> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn icon(&self) -> Node<impl Image> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn image(&self) -> Node<impl Image> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn in_reply_to(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn location(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn preview(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> } // also in link
|
|
||||||
fn published(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
|
||||||
fn replies(&self) -> Node<impl Collection> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn start_time(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
|
||||||
fn summary(&self) -> Option<&str> { None }
|
|
||||||
fn tag(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn updated(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
|
|
||||||
fn url(&self) -> Node<impl super::Link> { Node::empty() }
|
|
||||||
fn to(&self) -> Node<impl Link> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn bto(&self) -> Node<impl Link> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn cc(&self) -> Node<impl Link> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn bcc(&self) -> Node<impl Link> { Node::Empty::<serde_json::Value> }
|
|
||||||
fn media_type(&self) -> Option<&str> { None } // also in link
|
|
||||||
fn duration(&self) -> Option<&str> { None } // TODO how to parse xsd:duration ?
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Addressed : Object {
|
|
||||||
fn addressed(&self) -> Vec<String> {
|
|
||||||
let mut to = self.to().get_links();
|
|
||||||
to.append(&mut self.bto().get_links());
|
|
||||||
to.append(&mut self.cc().get_links());
|
|
||||||
to.append(&mut self.bcc().get_links());
|
|
||||||
to
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ObjectMut : super::BaseMut {
|
|
||||||
fn set_object_type(self, val: Option<ObjectType>) -> Self;
|
|
||||||
fn set_attachment(self, val: Node<impl Object>) -> Self;
|
|
||||||
fn set_attributed_to(self, val: Node<impl Actor>) -> Self;
|
|
||||||
fn set_audience(self, val: Node<impl Actor>) -> Self;
|
|
||||||
fn set_content(self, val: Option<&str>) -> Self; // TODO handle language maps
|
|
||||||
fn set_context(self, val: Node<impl Object>) -> Self;
|
|
||||||
fn set_name(self, val: Option<&str>) -> Self; // also in link // TODO handle language maps
|
|
||||||
fn set_end_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
|
||||||
fn set_generator(self, val: Node<impl Actor>) -> Self;
|
|
||||||
fn set_icon(self, val: Node<impl Image>) -> Self;
|
|
||||||
fn set_image(self, val: Node<impl Image>) -> Self;
|
|
||||||
fn set_in_reply_to(self, val: Node<impl Object>) -> Self;
|
|
||||||
fn set_location(self, val: Node<impl Object>) -> Self;
|
|
||||||
fn set_preview(self, val: Node<impl Object>) -> Self; // also in link
|
|
||||||
fn set_published(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
|
||||||
fn set_replies(self, val: Node<impl Collection>) -> Self;
|
|
||||||
fn set_start_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
|
||||||
fn set_summary(self, val: Option<&str>) -> Self;
|
|
||||||
fn set_tag(self, val: Node<impl Object>) -> Self;
|
|
||||||
fn set_updated(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
|
|
||||||
fn set_url(self, val: Node<impl super::Link>) -> Self;
|
|
||||||
fn set_to(self, val: Node<impl Link>) -> Self;
|
|
||||||
fn set_bto(self, val: Node<impl Link>) -> Self;
|
|
||||||
fn set_cc(self, val: Node<impl Link>) -> Self;
|
|
||||||
fn set_bcc(self, val: Node<impl Link>) -> Self;
|
|
||||||
fn set_media_type(self, val: Option<&str>) -> Self; // also in link
|
|
||||||
fn set_duration(self, val: Option<&str>) -> Self; // TODO how to parse xsd:duration ?
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Object for serde_json::Value {
|
|
||||||
|
|
||||||
getter! { object_type -> type ObjectType }
|
|
||||||
getter! { attachment -> node impl Object }
|
|
||||||
getter! { attributed_to::attributedTo -> node impl Actor }
|
|
||||||
getter! { audience -> node impl Actor }
|
|
||||||
getter! { content -> &str }
|
|
||||||
getter! { name -> &str }
|
|
||||||
getter! { end_time::endTime -> chrono::DateTime<chrono::Utc> }
|
|
||||||
getter! { generator -> node impl Actor }
|
|
||||||
getter! { icon -> node impl Image }
|
|
||||||
getter! { image -> node impl Image }
|
|
||||||
getter! { in_reply_to::inReplyTo -> node impl Object }
|
|
||||||
getter! { location -> node impl Object }
|
|
||||||
getter! { preview -> node impl Object }
|
|
||||||
getter! { published -> chrono::DateTime<chrono::Utc> }
|
|
||||||
getter! { replies -> node impl Collection }
|
|
||||||
getter! { start_time::startTime -> chrono::DateTime<chrono::Utc> }
|
|
||||||
getter! { summary -> &str }
|
|
||||||
getter! { tag -> node impl Object }
|
|
||||||
getter! { updated -> chrono::DateTime<chrono::Utc> }
|
|
||||||
getter! { to -> node impl Link }
|
|
||||||
getter! { bto -> node impl Link }
|
|
||||||
getter! { cc -> node impl Link }
|
|
||||||
getter! { bcc -> node impl Link }
|
|
||||||
getter! { media_type -> &str }
|
|
||||||
getter! { duration -> &str }
|
|
||||||
getter! { url -> node impl super::Link }
|
|
||||||
|
|
||||||
// TODO Mastodon doesn't use a "context" field on the object but makes up a new one!!
|
|
||||||
fn context(&self) -> Node<impl Object> {
|
|
||||||
match self.get("context") {
|
|
||||||
Some(x) => Node::from(x.clone()),
|
|
||||||
None => match self.get("conversation") {
|
|
||||||
Some(x) => Node::from(x.clone()),
|
|
||||||
None => Node::empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Addressed for serde_json::Value {}
|
|
||||||
|
|
||||||
impl ObjectMut for serde_json::Value {
|
|
||||||
setter! { object_type -> type ObjectType }
|
|
||||||
setter! { attachment -> node impl Object }
|
|
||||||
setter! { attributed_to::attributedTo -> node impl Actor }
|
|
||||||
setter! { audience -> node impl Actor }
|
|
||||||
setter! { content -> &str }
|
|
||||||
setter! { name -> &str }
|
|
||||||
setter! { end_time::endTime -> chrono::DateTime<chrono::Utc> }
|
|
||||||
setter! { generator -> node impl Actor }
|
|
||||||
setter! { icon -> node impl Image }
|
|
||||||
setter! { image -> node impl Image }
|
|
||||||
setter! { in_reply_to::inReplyTo -> node impl Object }
|
|
||||||
setter! { location -> node impl Object }
|
|
||||||
setter! { preview -> node impl Object }
|
|
||||||
setter! { published -> chrono::DateTime<chrono::Utc> }
|
|
||||||
setter! { replies -> node impl Collection }
|
|
||||||
setter! { start_time::startTime -> chrono::DateTime<chrono::Utc> }
|
|
||||||
setter! { summary -> &str }
|
|
||||||
setter! { tag -> node impl Object }
|
|
||||||
setter! { updated -> chrono::DateTime<chrono::Utc> }
|
|
||||||
setter! { to -> node impl Link }
|
|
||||||
setter! { bto -> node impl Link}
|
|
||||||
setter! { cc -> node impl Link }
|
|
||||||
setter! { bcc -> node impl Link }
|
|
||||||
setter! { media_type -> &str }
|
|
||||||
setter! { duration -> &str }
|
|
||||||
setter! { url -> node impl super::Link }
|
|
||||||
|
|
||||||
// TODO Mastodon doesn't use a "context" field on the object but makes up a new one!!
|
|
||||||
fn set_context(mut self, ctx: Node<impl Object>) -> Self {
|
|
||||||
if let Some(conversation) = ctx.id() {
|
|
||||||
crate::activitystream::macros::set_maybe_value(
|
|
||||||
&mut self, "conversation", Some(serde_json::Value::String(conversation.to_string())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
crate::activitystream::macros::set_maybe_node(
|
|
||||||
&mut self, "context", ctx
|
|
||||||
);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
pub use super::{
|
|
||||||
Base as _, BaseMut as _,
|
|
||||||
key::{PublicKey as _, PublicKeyMut as _},
|
|
||||||
link::{Link as _, LinkMut as _},
|
|
||||||
object::{
|
|
||||||
Object as _, ObjectMut as _,
|
|
||||||
tombstone::{Tombstone as _, TombstoneMut as _},
|
|
||||||
relationship::{Relationship as _, RelationshipMut as _},
|
|
||||||
profile::{Profile as _, /* ProfileMut as _ */}, // TODO!
|
|
||||||
place::{Place as _, PlaceMut as _},
|
|
||||||
actor::{Actor as _, ActorMut as _},
|
|
||||||
document::{
|
|
||||||
Document as _, DocumentMut as _, Image as _,
|
|
||||||
},
|
|
||||||
collection::{
|
|
||||||
Collection as _, CollectionMut as _,
|
|
||||||
page::{CollectionPage as _, CollectionPageMut as _},
|
|
||||||
},
|
|
||||||
activity::{
|
|
||||||
Activity as _, ActivityMut as _,
|
|
||||||
reject::{Reject as _, RejectMut as _},
|
|
||||||
offer::{Offer as _, OfferMut as _},
|
|
||||||
intransitive::{IntransitiveActivity as _, IntransitiveActivityMut as _},
|
|
||||||
ignore::{Ignore as _, IgnoreMut as _},
|
|
||||||
accept::{Accept as _, AcceptMut as _},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -4,7 +4,8 @@ use reqwest::header::{CONTENT_TYPE, USER_AGENT};
|
||||||
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, Order, QueryFilter, QueryOrder};
|
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, Order, QueryFilter, QueryOrder};
|
||||||
use tokio::{sync::broadcast, task::JoinHandle};
|
use tokio::{sync::broadcast, task::JoinHandle};
|
||||||
|
|
||||||
use crate::{activitypub::{activity::ap_activity, object::ap_object}, activitystream::{object::activity::ActivityMut, Node}, errors::UpubError, model, server::Context, VERSION};
|
use apb::{ActivityMut, Node};
|
||||||
|
use crate::{activitypub::{activity::ap_activity, object::ap_object}, errors::UpubError, model, server::Context, VERSION};
|
||||||
|
|
||||||
pub struct Dispatcher {
|
pub struct Dispatcher {
|
||||||
waker: broadcast::Sender<()>,
|
waker: broadcast::Sender<()>,
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -1,4 +1,3 @@
|
||||||
pub mod activitystream;
|
|
||||||
pub mod activitypub;
|
pub mod activitypub;
|
||||||
|
|
||||||
mod model;
|
mod model;
|
||||||
|
@ -16,8 +15,6 @@ use sea_orm_migration::MigratorTrait;
|
||||||
|
|
||||||
pub use errors::UpubResult as Result;
|
pub use errors::UpubResult as Result;
|
||||||
|
|
||||||
use crate::activitystream::{BaseType, ObjectType};
|
|
||||||
|
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
@ -102,9 +99,9 @@ async fn main() {
|
||||||
|
|
||||||
|
|
||||||
async fn fetch(db: &sea_orm::DatabaseConnection, uri: &str, save: bool) -> reqwest::Result<()> {
|
async fn fetch(db: &sea_orm::DatabaseConnection, uri: &str, save: bool) -> reqwest::Result<()> {
|
||||||
use crate::activitystream::{Base, Object};
|
use apb::{Base, Object};
|
||||||
|
|
||||||
let mut node = activitystream::Node::from(uri);
|
let mut node = apb::Node::from(uri);
|
||||||
tracing::info!("fetching object");
|
tracing::info!("fetching object");
|
||||||
node.fetch().await?;
|
node.fetch().await?;
|
||||||
tracing::info!("fetched node");
|
tracing::info!("fetched node");
|
||||||
|
@ -115,23 +112,23 @@ async fn fetch(db: &sea_orm::DatabaseConnection, uri: &str, save: bool) -> reqwe
|
||||||
|
|
||||||
if save {
|
if save {
|
||||||
match obj.base_type() {
|
match obj.base_type() {
|
||||||
Some(BaseType::Object(ObjectType::Actor(_))) => {
|
Some(apb::BaseType::Object(apb::ObjectType::Actor(_))) => {
|
||||||
model::user::Entity::insert(
|
model::user::Entity::insert(
|
||||||
model::user::Model::new(&obj).unwrap().into_active_model()
|
model::user::Model::new(obj).unwrap().into_active_model()
|
||||||
).exec(db).await.unwrap();
|
).exec(db).await.unwrap();
|
||||||
},
|
},
|
||||||
Some(BaseType::Object(ObjectType::Activity(_))) => {
|
Some(apb::BaseType::Object(apb::ObjectType::Activity(_))) => {
|
||||||
model::activity::Entity::insert(
|
model::activity::Entity::insert(
|
||||||
model::activity::Model::new(&obj).unwrap().into_active_model()
|
model::activity::Model::new(obj).unwrap().into_active_model()
|
||||||
).exec(db).await.unwrap();
|
).exec(db).await.unwrap();
|
||||||
},
|
},
|
||||||
Some(BaseType::Object(ObjectType::Note)) => {
|
Some(apb::BaseType::Object(apb::ObjectType::Note)) => {
|
||||||
model::object::Entity::insert(
|
model::object::Entity::insert(
|
||||||
model::object::Model::new(&obj).unwrap().into_active_model()
|
model::object::Model::new(obj).unwrap().into_active_model()
|
||||||
).exec(db).await.unwrap();
|
).exec(db).await.unwrap();
|
||||||
},
|
},
|
||||||
Some(BaseType::Object(t)) => tracing::warn!("not implemented: {:?}", t),
|
Some(apb::BaseType::Object(t)) => tracing::warn!("not implemented: {:?}", t),
|
||||||
Some(BaseType::Link(_)) => tracing::error!("fetched another link?"),
|
Some(apb::BaseType::Link(_)) => tracing::error!("fetched another link?"),
|
||||||
None => tracing::error!("no type on object"),
|
None => tracing::error!("no type on object"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
use crate::activitystream::object::activity::{Activity, ActivityType};
|
|
||||||
|
|
||||||
use super::Audience;
|
use super::Audience;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
@ -10,7 +8,7 @@ pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|
||||||
pub activity_type: ActivityType,
|
pub activity_type: apb::ActivityType,
|
||||||
pub actor: String,
|
pub actor: String,
|
||||||
pub object: Option<String>,
|
pub object: Option<String>,
|
||||||
|
|
||||||
|
@ -25,13 +23,13 @@ pub struct Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn new(activity: &impl Activity) -> Result<Self, super::FieldError> {
|
pub fn new(activity: &impl apb::Activity) -> Result<Self, super::FieldError> {
|
||||||
Ok(Model {
|
Ok(Model {
|
||||||
id: activity.id().ok_or(super::FieldError("id"))?.to_string(),
|
id: activity.id().ok_or(super::FieldError("id"))?.to_string(),
|
||||||
activity_type: activity.activity_type().ok_or(super::FieldError("type"))?,
|
activity_type: activity.activity_type().ok_or(super::FieldError("type"))?,
|
||||||
actor: activity.actor().id().ok_or(super::FieldError("actor"))?.to_string(),
|
actor: activity.actor().id().ok_or(super::FieldError("actor"))?,
|
||||||
object: activity.object().id().map(|x| x.to_string()),
|
object: activity.object().id(),
|
||||||
target: activity.target().id().map(|x| x.to_string()),
|
target: activity.target().id(),
|
||||||
published: activity.published().unwrap_or(chrono::Utc::now()),
|
published: activity.published().unwrap_or(chrono::Utc::now()),
|
||||||
to: activity.to().into(),
|
to: activity.to().into(),
|
||||||
bto: activity.bto().into(),
|
bto: activity.bto().into(),
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String, count: u64)
|
||||||
inbox: None,
|
inbox: None,
|
||||||
shared_inbox: None,
|
shared_inbox: None,
|
||||||
outbox: None,
|
outbox: None,
|
||||||
actor_type: crate::activitystream::object::actor::ActorType::Person,
|
actor_type: apb::ActorType::Person,
|
||||||
created: chrono::Utc::now(),
|
created: chrono::Utc::now(),
|
||||||
updated: chrono::Utc::now(),
|
updated: chrono::Utc::now(),
|
||||||
private_key: Some(std::str::from_utf8(&key.private_key_to_pem().unwrap()).unwrap().to_string()),
|
private_key: Some(std::str::from_utf8(&key.private_key_to_pem().unwrap()).unwrap().to_string()),
|
||||||
|
@ -55,7 +55,7 @@ pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String, count: u64)
|
||||||
object::Entity::insert(object::ActiveModel {
|
object::Entity::insert(object::ActiveModel {
|
||||||
id: Set(format!("{domain}/objects/{oid}")),
|
id: Set(format!("{domain}/objects/{oid}")),
|
||||||
name: Set(None),
|
name: Set(None),
|
||||||
object_type: Set(crate::activitystream::object::ObjectType::Note),
|
object_type: Set(apb::ObjectType::Note),
|
||||||
attributed_to: Set(Some(format!("{domain}/users/test"))),
|
attributed_to: Set(Some(format!("{domain}/users/test"))),
|
||||||
summary: Set(None),
|
summary: Set(None),
|
||||||
context: Set(Some(context.clone())),
|
context: Set(Some(context.clone())),
|
||||||
|
@ -72,7 +72,7 @@ pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String, count: u64)
|
||||||
|
|
||||||
activity::Entity::insert(activity::ActiveModel {
|
activity::Entity::insert(activity::ActiveModel {
|
||||||
id: Set(format!("{domain}/activities/{aid}")),
|
id: Set(format!("{domain}/activities/{aid}")),
|
||||||
activity_type: Set(crate::activitystream::object::activity::ActivityType::Create),
|
activity_type: Set(apb::ActivityType::Create),
|
||||||
actor: Set(format!("{domain}/users/test")),
|
actor: Set(format!("{domain}/users/test")),
|
||||||
object: Set(Some(format!("{domain}/objects/{oid}"))),
|
object: Set(Some(format!("{domain}/objects/{oid}"))),
|
||||||
target: Set(None),
|
target: Set(None),
|
||||||
|
|
|
@ -28,7 +28,7 @@ impl From<FieldError> for axum::http::StatusCode {
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, sea_orm::FromJsonQueryResult)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, sea_orm::FromJsonQueryResult)]
|
||||||
pub struct Audience(pub Vec<String>);
|
pub struct Audience(pub Vec<String>);
|
||||||
|
|
||||||
use crate::activitystream::{Link, Node};
|
use apb::{Link, Node};
|
||||||
impl<T : Link> From<Node<T>> for Audience {
|
impl<T : Link> From<Node<T>> for Audience {
|
||||||
fn from(value: Node<T>) -> Self {
|
fn from(value: Node<T>) -> Self {
|
||||||
Audience(
|
Audience(
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
use crate::activitystream::object::ObjectType;
|
|
||||||
|
|
||||||
use super::Audience;
|
use super::Audience;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
@ -9,7 +7,7 @@ use super::Audience;
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub object_type: ObjectType,
|
pub object_type: apb::ObjectType,
|
||||||
pub attributed_to: Option<String>,
|
pub attributed_to: Option<String>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub summary: Option<String>,
|
pub summary: Option<String>,
|
||||||
|
@ -26,15 +24,15 @@ pub struct Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn new(object: &impl crate::activitystream::object::Object) -> Result<Self, super::FieldError> {
|
pub fn new(object: &impl apb::Object) -> Result<Self, super::FieldError> {
|
||||||
Ok(Model {
|
Ok(Model {
|
||||||
id: object.id().ok_or(super::FieldError("id"))?.to_string(),
|
id: object.id().ok_or(super::FieldError("id"))?.to_string(),
|
||||||
object_type: object.object_type().ok_or(super::FieldError("type"))?,
|
object_type: object.object_type().ok_or(super::FieldError("type"))?,
|
||||||
attributed_to: object.attributed_to().id().map(|x| x.to_string()),
|
attributed_to: object.attributed_to().id(),
|
||||||
name: object.name().map(|x| x.to_string()),
|
name: object.name().map(|x| x.to_string()),
|
||||||
summary: object.summary().map(|x| x.to_string()),
|
summary: object.summary().map(|x| x.to_string()),
|
||||||
content: object.content().map(|x| x.to_string()),
|
content: object.content().map(|x| x.to_string()),
|
||||||
context: object.context().id().map(|x| x.to_string()),
|
context: object.context().id(),
|
||||||
published: object.published().ok_or(super::FieldError("published"))?,
|
published: object.published().ok_or(super::FieldError("published"))?,
|
||||||
comments: 0,
|
comments: 0,
|
||||||
likes: 0,
|
likes: 0,
|
||||||
|
|
|
@ -11,5 +11,6 @@ pub struct Model {
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {}
|
pub enum Relation {}
|
||||||
|
// TODO how to represent this User-to-User relation in sea orm??
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use crate::activitystream::key::PublicKey as _;
|
|
||||||
|
|
||||||
use crate::{activitypub, activitystream::object::{collection::Collection, actor::{Actor, ActorType}}};
|
use apb::{Collection, Actor, PublicKey, ActorType};
|
||||||
|
use crate::activitypub;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "users")]
|
#[sea_orm(table_name = "users")]
|
||||||
|
@ -46,13 +46,13 @@ impl Model {
|
||||||
actor_type: object.actor_type().ok_or(super::FieldError("type"))?,
|
actor_type: object.actor_type().ok_or(super::FieldError("type"))?,
|
||||||
name: object.name().map(|x| x.to_string()),
|
name: object.name().map(|x| x.to_string()),
|
||||||
summary: object.summary().map(|x| x.to_string()),
|
summary: object.summary().map(|x| x.to_string()),
|
||||||
icon: object.icon().id().map(|x| x.to_string()),
|
icon: object.icon().id(),
|
||||||
image: object.image().id().map(|x| x.to_string()),
|
image: object.image().id(),
|
||||||
inbox: object.inbox().id().map(|x| x.to_string()),
|
inbox: object.inbox().id(),
|
||||||
outbox: object.inbox().id().map(|x| x.to_string()),
|
outbox: object.inbox().id(),
|
||||||
shared_inbox: None, // TODO!!! parse endpoints
|
shared_inbox: None, // TODO!!! parse endpoints
|
||||||
followers: object.followers().id().map(|x| x.to_string()),
|
followers: object.followers().id(),
|
||||||
following: object.following().id().map(|x| x.to_string()),
|
following: object.following().id(),
|
||||||
created: object.published().unwrap_or(chrono::Utc::now()),
|
created: object.published().unwrap_or(chrono::Utc::now()),
|
||||||
updated: chrono::Utc::now(),
|
updated: chrono::Utc::now(),
|
||||||
following_count: object.following().get().map(|f| f.total_items().unwrap_or(0)).unwrap_or(0) as i64,
|
following_count: object.following().get().map(|f| f.total_items().unwrap_or(0)).unwrap_or(0) as i64,
|
||||||
|
|
|
@ -3,7 +3,8 @@ use std::{str::Utf8Error, sync::Arc};
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, DbErr, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, DbErr, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
||||||
|
|
||||||
use crate::{activitypub::{jsonld::LD, PUBLIC_TARGET}, activitystream::{object::collection::{page::CollectionPageMut, CollectionMut, CollectionType}, BaseMut, Node}, dispatcher::Dispatcher, fetcher::Fetcher, model};
|
use crate::{activitypub::{jsonld::LD, PUBLIC_TARGET}, dispatcher::Dispatcher, fetcher::Fetcher, model};
|
||||||
|
use apb::{CollectionPageMut, CollectionMut, CollectionType, BaseMut, Node};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Context(Arc<ContextInner>);
|
pub struct Context(Arc<ContextInner>);
|
||||||
|
@ -215,7 +216,7 @@ impl Context {
|
||||||
.set_total_items(total_items)
|
.set_total_items(total_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ap_collection_page(&self, id: &str, offset: u64, limit: u64, items: Vec<Node<serde_json::Value>>) -> serde_json::Value {
|
pub fn ap_collection_page(&self, id: &str, offset: u64, limit: u64, items: Vec<serde_json::Value>) -> serde_json::Value {
|
||||||
serde_json::Value::new_object()
|
serde_json::Value::new_object()
|
||||||
.set_id(Some(&format!("{id}?offset={offset}")))
|
.set_id(Some(&format!("{id}?offset={offset}")))
|
||||||
.set_collection_type(Some(CollectionType::OrderedCollectionPage))
|
.set_collection_type(Some(CollectionType::OrderedCollectionPage))
|
||||||
|
|
Loading…
Reference in a new issue