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:
əlemi 2024-04-06 16:56:13 +02:00
parent b5867b90ac
commit 520c8eff3a
Signed by: alemi
GPG key ID: A4895B84D311642C
51 changed files with 3593 additions and 829 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
/apb/target

View file

@ -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

File diff suppressed because it is too large Load diff

23
apb/Cargo.toml Normal file
View 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
View 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
View 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},
};

View file

@ -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
View 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,
}
}
}

View 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
View 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
}
}

View 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 }
}

View 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 }
}

View file

@ -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
View 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
}
}

View file

@ -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 {

View file

@ -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;
} }

View file

@ -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> {

View file

@ -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()
)) ))
} }

View file

@ -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) {

View file

@ -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> {

View file

@ -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()
)) ))
}, },

View file

@ -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?;
}, },

View file

@ -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};

View file

@ -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()
}; };

View file

@ -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 }
}

View file

@ -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,
}
}
}

View file

@ -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 }
}

View file

@ -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
}
}

View file

@ -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 }
}

View file

@ -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 }
}

View file

@ -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
}
}

View file

@ -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 _},
},
}
};

View file

@ -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<()>,

View file

@ -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"),
} }
} }

View file

@ -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(),

View file

@ -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),

View file

@ -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(

View file

@ -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,

View file

@ -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 {}

View file

@ -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,

View file

@ -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))