diff --git a/src/model/activity.rs b/src/model/activity.rs index 421336ec..ec10c923 100644 --- a/src/model/activity.rs +++ b/src/model/activity.rs @@ -1,5 +1,8 @@ +use apb::{ActivityMut, BaseMut, ObjectMut}; use sea_orm::entity::prelude::*; +use crate::routes::activitypub::jsonld::LD; + use super::Audience; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -37,6 +40,20 @@ impl Model { bcc: activity.bcc().into(), }) } + + pub fn ap(self) -> serde_json::Value { + serde_json::Value::new_object() + .set_id(Some(&self.id)) + .set_activity_type(Some(self.activity_type)) + .set_actor(apb::Node::link(self.actor)) + .set_object(apb::Node::maybe_link(self.object)) + .set_target(apb::Node::maybe_link(self.target)) + .set_published(Some(self.published)) + .set_to(apb::Node::links(self.to.0.clone())) + .set_bto(apb::Node::Empty) + .set_cc(apb::Node::links(self.cc.0.clone())) + .set_bcc(apb::Node::Empty) + } } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/src/model/addressing.rs b/src/model/addressing.rs index 5c517c7c..41c6948f 100644 --- a/src/model/addressing.rs +++ b/src/model/addressing.rs @@ -1,8 +1,6 @@ use apb::{ActivityMut, Node}; use sea_orm::{entity::prelude::*, FromQueryResult, Iterable, QuerySelect, SelectColumns}; -use crate::routes::activitypub::{activity::ap_activity, object::ap_object}; - #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "addressing")] pub struct Model { @@ -67,9 +65,10 @@ pub struct EmbeddedActivity { impl From for serde_json::Value { fn from(value: EmbeddedActivity) -> Self { + let a = value.activity.ap(); match value.object { - Some(o) => ap_activity(value.activity).set_object(Node::object(ap_object(o))), - None => ap_activity(value.activity) + None => a, + Some(o) => a.set_object(Node::object(o.ap())), } } } diff --git a/src/model/object.rs b/src/model/object.rs index 09ba2efd..eeceba95 100644 --- a/src/model/object.rs +++ b/src/model/object.rs @@ -1,5 +1,8 @@ +use apb::{BaseMut, ObjectMut}; use sea_orm::entity::prelude::*; +use crate::routes::activitypub::jsonld::LD; + use super::Audience; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -45,6 +48,23 @@ impl Model { bcc: object.bcc().into(), }) } + // TODO this is used outside /routes, maybe move in model? + pub fn ap(self) -> serde_json::Value { + serde_json::Value::new_object() + .set_id(Some(&self.id)) + .set_object_type(Some(self.object_type)) + .set_attributed_to(apb::Node::maybe_link(self.attributed_to)) + .set_name(self.name.as_deref()) + .set_summary(self.summary.as_deref()) + .set_content(self.content.as_deref()) + .set_context(apb::Node::maybe_link(self.context.clone())) + .set_in_reply_to(apb::Node::maybe_link(self.in_reply_to.clone())) + .set_published(Some(self.published)) + .set_to(apb::Node::links(self.to.0.clone())) + .set_bto(apb::Node::Empty) + .set_cc(apb::Node::links(self.cc.0.clone())) + .set_bcc(apb::Node::Empty) + } } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/src/model/user.rs b/src/model/user.rs index be48ac55..37490dc0 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,6 +1,8 @@ use sea_orm::entity::prelude::*; -use apb::{Collection, Object, Actor, PublicKey, ActorType}; +use apb::{Actor, ActorMut, ActorType, BaseMut, Collection, DocumentMut, Object, ObjectMut, PublicKey, PublicKeyMut}; + +use crate::routes::activitypub::jsonld::LD; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "users")] @@ -62,6 +64,38 @@ impl Model { private_key: None, // there's no way to transport privkey over AP json, must come from DB }) } + + pub fn ap(self) -> serde_json::Value { + serde_json::Value::new_object() + .set_id(Some(&self.id)) + .set_actor_type(Some(self.actor_type)) + .set_name(self.name.as_deref()) + .set_summary(self.summary.as_deref()) + .set_icon(apb::Node::maybe_object(self.icon.map(|i| + serde_json::Value::new_object() + .set_document_type(Some(apb::DocumentType::Image)) + .set_url(apb::Node::link(i.clone())) + ))) + .set_image(apb::Node::maybe_object(self.image.map(|i| + serde_json::Value::new_object() + .set_document_type(Some(apb::DocumentType::Image)) + .set_url(apb::Node::link(i.clone())) + ))) + .set_published(Some(self.created)) + .set_preferred_username(Some(&self.preferred_username)) + .set_inbox(apb::Node::maybe_link(self.inbox)) + .set_outbox(apb::Node::maybe_link(self.outbox)) + .set_following(apb::Node::maybe_link(self.following)) + .set_followers(apb::Node::maybe_link(self.followers)) + .set_public_key(apb::Node::object( + serde_json::Value::new_object() + .set_id(Some(&format!("{}#main-key", self.id))) + .set_owner(Some(&self.id)) + .set_public_key_pem(&self.public_key) + )) + .set_discoverable(Some(true)) + .set_endpoints(apb::Node::Empty) + } } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/src/routes/activitypub/activity.rs b/src/routes/activitypub/activity.rs index 271239bd..e2d59a68 100644 --- a/src/routes/activitypub/activity.rs +++ b/src/routes/activitypub/activity.rs @@ -1,25 +1,9 @@ use axum::extract::{Path, Query, State}; use sea_orm::{ColumnTrait, QueryFilter}; use crate::{errors::UpubError, model::{self, addressing::EmbeddedActivity}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}}; -use apb::{ActivityMut, ObjectMut, BaseMut, Node}; use super::{jsonld::LD, JsonLD, TryFetch}; -// TODO this is used outside /routes, maybe move in model? -pub fn ap_activity(activity: model::activity::Model) -> serde_json::Value { - serde_json::Value::new_object() - .set_id(Some(&activity.id)) - .set_activity_type(Some(activity.activity_type)) - .set_actor(Node::link(activity.actor)) - .set_object(Node::maybe_link(activity.object)) - .set_target(Node::maybe_link(activity.target)) - .set_published(Some(activity.published)) - .set_to(Node::links(activity.to.0.clone())) - .set_bto(Node::Empty) - .set_cc(Node::links(activity.cc.0.clone())) - .set_bcc(Node::Empty) -} - pub async fn view( State(ctx): State, Path(id): Path, @@ -40,7 +24,7 @@ pub async fn view( { Some(activity) => Ok(JsonLD(serde_json::Value::from(activity).ld_context())), None => if auth.is_local() && query.fetch && !ctx.is_local(&aid) { - Ok(JsonLD(ap_activity(ctx.fetch_activity(&aid).await?).ld_context())) + Ok(JsonLD(ctx.fetch_activity(&aid).await?.ap().ld_context())) } else { Err(UpubError::not_found()) }, diff --git a/src/routes/activitypub/object.rs b/src/routes/activitypub/object.rs deleted file mode 100644 index dd8de76e..00000000 --- a/src/routes/activitypub/object.rs +++ /dev/null @@ -1,53 +0,0 @@ -use axum::extract::{Path, Query, State}; -use sea_orm::{ColumnTrait, QueryFilter}; - -use apb::{ObjectMut, BaseMut, Node}; -use crate::{errors::UpubError, model::{self, addressing::EmbeddedActivity}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}}; - -use super::{jsonld::LD, JsonLD, TryFetch}; - -// TODO this is used outside /routes, maybe move in model? -pub fn ap_object(object: model::object::Model) -> serde_json::Value { - serde_json::Value::new_object() - .set_id(Some(&object.id)) - .set_object_type(Some(object.object_type)) - .set_attributed_to(Node::maybe_link(object.attributed_to)) - .set_name(object.name.as_deref()) - .set_summary(object.summary.as_deref()) - .set_content(object.content.as_deref()) - .set_context(Node::maybe_link(object.context.clone())) - .set_in_reply_to(Node::maybe_link(object.in_reply_to.clone())) - .set_published(Some(object.published)) - .set_to(Node::links(object.to.0.clone())) - .set_bto(Node::Empty) - .set_cc(Node::links(object.cc.0.clone())) - .set_bcc(Node::Empty) -} - -pub async fn view( - State(ctx): State, - Path(id): Path, - AuthIdentity(auth): AuthIdentity, - Query(query): Query, -) -> crate::Result> { - let oid = if id.starts_with('+') { - format!("https://{}", id.replacen('+', "", 1).replace('@', "/")) - } else { - ctx.oid(id.clone()) - }; - match model::addressing::Entity::find_activities() - .filter(model::object::Column::Id.eq(&oid)) - .filter(auth.filter_condition()) - .into_model::() - .one(ctx.db()) - .await? - { - Some(EmbeddedActivity { activity: _, object: Some(object) }) => Ok(JsonLD(ap_object(object).ld_context())), - Some(EmbeddedActivity { activity: _, object: None }) => Err(UpubError::not_found()), - None => if auth.is_local() && query.fetch && !ctx.is_local(&oid) { - Ok(JsonLD(ap_object(ctx.fetch_object(&oid).await?).ld_context())) - } else { - Err(UpubError::not_found()) - }, - } -} diff --git a/src/routes/activitypub/user/following.rs b/src/routes/activitypub/user/following.rs index c66683c2..65ed3adb 100644 --- a/src/routes/activitypub/user/following.rs +++ b/src/routes/activitypub/user/following.rs @@ -1,5 +1,5 @@ use axum::{extract::{Path, Query, State}, http::StatusCode}; -use sea_orm::{ColumnTrait, Condition, EntityTrait, PaginatorTrait, QueryFilter, QuerySelect, SelectColumns}; +use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QuerySelect, SelectColumns}; use crate::{routes::activitypub::{jsonld::LD, JsonLD, Pagination}, model, server::Context, url}; @@ -11,7 +11,7 @@ pub async fn get( ) -> Result, StatusCode> { let follow___ = if OUTGOING { "following" } else { "followers" }; let count = model::relation::Entity::find() - .filter(Condition::all().add(if OUTGOING { Follower } else { Following }.eq(ctx.uid(id.clone())))) + .filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(id.clone()))) .count(ctx.db()).await.unwrap_or_else(|e| { tracing::error!("failed counting {follow___} for {id}: {e}"); 0 @@ -33,7 +33,7 @@ pub async fn page( let limit = page.batch.unwrap_or(20).min(50); let offset = page.offset.unwrap_or(0); match model::relation::Entity::find() - .filter(Condition::all().add(if OUTGOING { Follower } else { Following }.eq(ctx.uid(id.clone())))) + .filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(id.clone()))) .select_only() .select_column(if OUTGOING { Following } else { Follower }) .limit(limit) diff --git a/src/routes/activitypub/user/inbox.rs b/src/routes/activitypub/user/inbox.rs index da5a5cc7..99923824 100644 --- a/src/routes/activitypub/user/inbox.rs +++ b/src/routes/activitypub/user/inbox.rs @@ -1,6 +1,6 @@ use axum::{extract::{Path, Query, State}, http::StatusCode, Json}; -use sea_orm::{ColumnTrait, Condition, Order, QueryFilter, QueryOrder, QuerySelect}; +use sea_orm::{ColumnTrait, Order, QueryFilter, QueryOrder, QuerySelect}; use crate::{errors::UpubError, model::{self, addressing::EmbeddedActivity}, routes::activitypub::{jsonld::LD, JsonLD, Pagination}, server::{auth::{AuthIdentity, Identity}, Context}, url}; pub async fn get( @@ -35,7 +35,7 @@ pub async fn page( let limit = page.batch.unwrap_or(20).min(50); let offset = page.offset.unwrap_or(0); let activities = model::addressing::Entity::find_activities() - .filter(Condition::all().add(model::addressing::Column::Actor.eq(&uid))) + .filter(model::addressing::Column::Actor.eq(&uid)) .order_by(model::addressing::Column::Published, Order::Desc) .offset(offset) .limit(limit) diff --git a/src/routes/activitypub/user/mod.rs b/src/routes/activitypub/user/mod.rs index dec01eee..488abb66 100644 --- a/src/routes/activitypub/user/mod.rs +++ b/src/routes/activitypub/user/mod.rs @@ -7,42 +7,11 @@ pub mod following; use axum::extract::{Path, Query, State}; use sea_orm::EntityTrait; -use apb::{ActorMut, BaseMut, CollectionMut, DocumentMut, DocumentType, Node, ObjectMut, PublicKeyMut}; +use apb::{ActorMut, BaseMut, CollectionMut, Node}; use crate::{errors::UpubError, model::{self, user}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}, url}; use super::{jsonld::LD, JsonLD, TryFetch}; -pub fn ap_user(user: model::user::Model) -> serde_json::Value { - serde_json::Value::new_object() - .set_id(Some(&user.id)) - .set_actor_type(Some(user.actor_type)) - .set_name(user.name.as_deref()) - .set_summary(user.summary.as_deref()) - .set_icon(Node::maybe_object(user.icon.map(|i| - serde_json::Value::new_object() - .set_document_type(Some(DocumentType::Image)) - .set_url(Node::link(i.clone())) - ))) - .set_image(Node::maybe_object(user.image.map(|i| - serde_json::Value::new_object() - .set_document_type(Some(DocumentType::Image)) - .set_url(Node::link(i.clone())) - ))) - .set_published(Some(user.created)) - .set_preferred_username(Some(&user.preferred_username)) - .set_inbox(Node::maybe_link(user.inbox)) - .set_outbox(Node::maybe_link(user.outbox)) - .set_following(Node::maybe_link(user.following)) - .set_followers(Node::maybe_link(user.followers)) - .set_public_key(Node::object( - serde_json::Value::new_object() - .set_id(Some(&format!("{}#main-key", user.id))) - .set_owner(Some(&user.id)) - .set_public_key_pem(&user.public_key) - )) - .set_discoverable(Some(true)) - .set_endpoints(Node::Empty) -} pub async fn view( State(ctx) : State, @@ -61,7 +30,7 @@ pub async fn view( { // local user Some((user, Some(cfg))) => { - Ok(JsonLD(ap_user(user.clone()) // ew ugly clone TODO + Ok(JsonLD(user.clone().ap() // ew ugly clone TODO .set_inbox(Node::link(url!(ctx, "/users/{id}/inbox"))) // TODO unread activities as count .set_outbox(Node::object( serde_json::Value::new_object() @@ -101,9 +70,9 @@ pub async fn view( )) }, // remote user TODDO doesn't work? - Some((user, None)) => Ok(JsonLD(ap_user(user).ld_context())), + Some((user, None)) => Ok(JsonLD(user.ap().ld_context())), None => if auth.is_local() && query.fetch && !ctx.is_local(&uid) { - Ok(JsonLD(ap_user(ctx.fetch_user(&uid).await?).ld_context())) + Ok(JsonLD(ctx.fetch_user(&uid).await?.ap().ld_context())) } else { Err(UpubError::not_found()) }, diff --git a/src/routes/activitypub/user/outbox.rs b/src/routes/activitypub/user/outbox.rs index e102d8ee..127a00fc 100644 --- a/src/routes/activitypub/user/outbox.rs +++ b/src/routes/activitypub/user/outbox.rs @@ -1,5 +1,5 @@ use axum::{extract::{Path, Query, State}, http::StatusCode, Json}; -use sea_orm::{ColumnTrait, Condition, Order, QueryFilter, QueryOrder, QuerySelect}; +use sea_orm::{ColumnTrait, Order, QueryFilter, QueryOrder, QuerySelect}; use apb::{server::Outbox, AcceptType, ActivityType, Base, BaseType, ObjectType, RejectType}; use crate::{errors::UpubError, model::{self, addressing::EmbeddedActivity}, routes::activitypub::{jsonld::LD, CreationResult, JsonLD, Pagination}, server::{auth::{AuthIdentity, Identity}, Context}, url}; @@ -28,7 +28,7 @@ pub async fn page( let offset = page.offset.unwrap_or(0); match model::addressing::Entity::find_activities() - .filter(Condition::all().add(model::activity::Column::Actor.eq(&uid))) + .filter(model::activity::Column::Actor.eq(&uid)) .filter(auth.filter_condition()) .order_by(model::addressing::Column::Published, Order::Desc) .limit(limit) diff --git a/src/routes/mastodon/accounts.rs b/src/routes/mastodon/accounts.rs index 1af7d8f9..d8935570 100644 --- a/src/routes/mastodon/accounts.rs +++ b/src/routes/mastodon/accounts.rs @@ -1,6 +1,6 @@ use axum::{extract::{Path, Query, State}, http::StatusCode, Json}; use mastodon_async_entities::{account::{Account, AccountId}, status::Status}; -use sea_orm::{ColumnTrait, Condition, EntityTrait, Order, QueryFilter, QueryOrder}; +use sea_orm::{ColumnTrait, EntityTrait, Order, QueryFilter, QueryOrder}; use crate::{model, server::{auth::AuthIdentity, Context}}; @@ -71,7 +71,7 @@ pub async fn statuses( ) -> Result>, StatusCode> { let uid = ctx.uid(id); model::addressing::Entity::find_activities() - .filter(Condition::all().add(model::activity::Column::Actor.eq(uid))) + .filter(model::activity::Column::Actor.eq(uid)) .filter(auth.filter_condition()) .order_by(model::addressing::Column::Published, Order::Desc); diff --git a/src/server/auth.rs b/src/server/auth.rs index 8e2176e3..925c60b3 100644 --- a/src/server/auth.rs +++ b/src/server/auth.rs @@ -77,7 +77,7 @@ where if auth_header.starts_with("Bearer ") { match model::session::Entity::find_by_id(auth_header.replace("Bearer ", "")) - .filter(Condition::all().add(model::session::Column::Expires.gt(chrono::Utc::now()))) + .filter(model::session::Column::Expires.gt(chrono::Utc::now())) .one(ctx.db()) .await { diff --git a/src/server/context.rs b/src/server/context.rs index 3e0ffba0..214ec070 100644 --- a/src/server/context.rs +++ b/src/server/context.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use apb::{BaseMut, CollectionMut, CollectionPageMut}; use openssl::rsa::Rsa; -use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set}; use crate::{model, routes::activitypub::jsonld::LD}; @@ -134,7 +134,7 @@ impl Context { if target.ends_with("/followers") { let target_id = target.replace("/followers", ""); model::relation::Entity::find() - .filter(Condition::all().add(model::relation::Column::Following.eq(target_id))) + .filter(model::relation::Column::Following.eq(target_id)) .select_only() .select_column(model::relation::Column::Follower) .into_tuple::() diff --git a/src/server/dispatcher.rs b/src/server/dispatcher.rs index dc60a1de..32ae7fce 100644 --- a/src/server/dispatcher.rs +++ b/src/server/dispatcher.rs @@ -1,9 +1,9 @@ use reqwest::Method; -use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, Order, QueryFilter, QueryOrder}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, Order, QueryFilter, QueryOrder}; use tokio::{sync::broadcast, task::JoinHandle}; use apb::{ActivityMut, Node}; -use crate::{errors::UpubError, model, routes::activitypub::{activity::ap_activity, object::ap_object}, server::{fetcher::Fetcher, Context}}; +use crate::{errors::UpubError, model, server::{fetcher::Fetcher, Context}}; pub struct Dispatcher { waker: broadcast::Sender<()>, @@ -39,7 +39,7 @@ impl Dispatcher { async fn worker(db: DatabaseConnection, domain: String, poll_interval: u64, mut waker: broadcast::Receiver<()>) -> Result<(), UpubError> { loop { let Some(delivery) = model::delivery::Entity::find() - .filter(Condition::all().add(model::delivery::Column::NotBefore.lte(chrono::Utc::now()))) + .filter(model::delivery::Column::NotBefore.lte(chrono::Utc::now())) .order_by(model::delivery::Column::NotBefore, Order::Asc) .one(&db) .await? @@ -76,8 +76,8 @@ async fn worker(db: DatabaseConnection, domain: String, poll_interval: u64, mut .one(&db) .await? // TODO probably should not fail here and at least re-insert the delivery { - Some((activity, Some(object))) => ap_activity(activity).set_object(Node::object(ap_object(object))), - Some((activity, None)) => ap_activity(activity), + Some((activity, Some(object))) => activity.ap().set_object(Node::object(object.ap())), + Some((activity, None)) => activity.ap(), None => { tracing::warn!("skipping dispatch for deleted object {}", delivery.activity); continue;