diff --git a/src/routes/activitypub/inbox.rs b/src/routes/activitypub/inbox.rs index 921f7c2b..24ed4237 100644 --- a/src/routes/activitypub/inbox.rs +++ b/src/routes/activitypub/inbox.rs @@ -1,4 +1,4 @@ -use apb::{server::Inbox, ActivityType, Base, BaseType, ObjectType}; +use apb::{server::Inbox, Activity, ActivityType, Base, BaseType, ObjectType}; use axum::{extract::{Query, State}, http::StatusCode, Json}; use sea_orm::{Order, QueryFilter, QueryOrder, QuerySelect}; @@ -52,48 +52,27 @@ pub async fn post( Identity::Local(_user) => return Err(UpubError::forbidden()), Identity::Anonymous => return Err(UpubError::unauthorized()), } - match activity.base_type() { - None => { Err(StatusCode::BAD_REQUEST.into()) }, - Some(BaseType::Link(_x)) => { - tracing::warn!("skipping remote activity: {}", serde_json::to_string_pretty(&activity).unwrap()); - Err(StatusCode::UNPROCESSABLE_ENTITY.into()) // we could but not yet - }, - - Some(BaseType::Object(ObjectType::Activity(ActivityType::Activity))) => { + // TODO we could process Links and bare Objects maybe, but probably out of AP spec? + match activity.activity_type().ok_or_else(UpubError::bad_request)? { + ActivityType::Activity => { tracing::warn!("skipping unprocessable base activity: {}", serde_json::to_string_pretty(&activity).unwrap()); Err(StatusCode::UNPROCESSABLE_ENTITY.into()) // won't ingest useless stuff }, - Some(BaseType::Object(ObjectType::Activity(ActivityType::Delete))) => - Ok(ctx.delete(activity).await?), + ActivityType::Delete => Ok(ctx.delete(activity).await?), + ActivityType::Follow => Ok(ctx.follow(activity).await?), + ActivityType::Accept(_) => Ok(ctx.accept(activity).await?), + ActivityType::Reject(_) => Ok(ctx.reject(activity).await?), + ActivityType::Like => Ok(ctx.like(activity).await?), + ActivityType::Create => Ok(ctx.create(activity).await?), + ActivityType::Update => Ok(ctx.update(activity).await?), + ActivityType::Undo => Ok(ctx.undo(activity).await?), + // ActivityType::Announce => Ok(ctx.announce(activity).await?), - Some(BaseType::Object(ObjectType::Activity(ActivityType::Follow))) => - Ok(ctx.follow(activity).await?), - - Some(BaseType::Object(ObjectType::Activity(ActivityType::Accept(_)))) => - Ok(ctx.accept(activity).await?), - - Some(BaseType::Object(ObjectType::Activity(ActivityType::Reject(_)))) => - Ok(ctx.reject(activity).await?), - - Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => - Ok(ctx.like(activity).await?), - - Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) => - Ok(ctx.create(activity).await?), - - Some(BaseType::Object(ObjectType::Activity(ActivityType::Update))) => - Ok(ctx.update(activity).await?), - - Some(BaseType::Object(ObjectType::Activity(_x))) => { + _x => { tracing::info!("received unimplemented activity on inbox: {}", serde_json::to_string_pretty(&activity).unwrap()); Err(StatusCode::NOT_IMPLEMENTED.into()) }, - - Some(_x) => { - tracing::warn!("ignoring non-activity object in inbox: {}", serde_json::to_string_pretty(&activity).unwrap()); - Err(StatusCode::UNPROCESSABLE_ENTITY.into()) - } } } diff --git a/src/server/inbox.rs b/src/server/inbox.rs index 4416abbd..96718172 100644 --- a/src/server/inbox.rs +++ b/src/server/inbox.rs @@ -1,5 +1,6 @@ use apb::{target::Addressed, Activity, Base, Object}; -use sea_orm::{sea_query::Expr, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set}; +use reqwest::StatusCode; +use sea_orm::{sea_query::Expr, ColumnTrait, Condition, EntityTrait, IntoActiveModel, QueryFilter, Set}; use crate::{errors::{LoggableError, UpubError}, model}; @@ -181,7 +182,48 @@ impl apb::server::Inbox for Context { Ok(()) } - async fn undo(&self, _activity: serde_json::Value) -> crate::Result<()> { - todo!() + async fn undo(&self, activity: serde_json::Value) -> crate::Result<()> { + let uid = activity.actor().id().ok_or_else(UpubError::bad_request)?; + // TODO in theory we could work with just object_id but right now only accept embedded + let undone_activity = activity.object().extract().ok_or_else(UpubError::bad_request)?; + let undone_aid = undone_activity.id().ok_or_else(UpubError::bad_request)?; + let undone_object_id = undone_activity.object().id().ok_or_else(UpubError::bad_request)?; + let activity_type = undone_activity.activity_type().ok_or_else(UpubError::bad_request)?; + + match activity_type { + apb::ActivityType::Like => { + model::like::Entity::delete_many() + .filter( + Condition::all() + .add(model::like::Column::Actor.eq(&uid)) + .add(model::like::Column::Likes.eq(&undone_object_id)) + ) + .exec(self.db()) + .await?; + model::object::Entity::update_many() + .filter(model::object::Column::Id.eq(&undone_object_id)) + .col_expr(model::object::Column::Likes, Expr::col(model::object::Column::Likes).sub(1)) + .exec(self.db()) + .await?; + }, + apb::ActivityType::Follow => { + model::relation::Entity::delete_many() + .filter( + Condition::all() + .add(model::relation::Column::Follower.eq(&uid)) + .add(model::relation::Column::Following.eq(&undone_object_id)) + ) + .exec(self.db()) + .await?; + }, + _ => { + tracing::error!("received 'Undo' for unimplemented activity: {}", serde_json::to_string_pretty(&activity).unwrap()); + return Err(StatusCode::NOT_IMPLEMENTED.into()); + }, + } + + model::activity::Entity::delete_by_id(undone_aid).exec(self.db()).await?; + + Ok(()) } }