forked from alemi/upub
chore: separated outbox business logic in methods
dumped all in server but temporary, now can be properly modularized also fixed endpoints
This commit is contained in:
parent
9d3376a1f4
commit
7ce872cfff
5 changed files with 262 additions and 214 deletions
|
@ -129,3 +129,14 @@ pub async fn auth(State(ctx): State<Context>, Json(login): Json<LoginForm>) -> R
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[axum::async_trait]
|
||||||
|
pub trait APOutbox {
|
||||||
|
async fn post_note(&self, uid: String, object: serde_json::Value) -> crate::Result<String>;
|
||||||
|
async fn post_activity(&self, uid: String, activity: serde_json::Value) -> crate::Result<String>;
|
||||||
|
async fn like(&self, uid: String, activity: serde_json::Value) -> crate::Result<String>;
|
||||||
|
async fn follow(&self, uid: String, activity: serde_json::Value) -> crate::Result<String>;
|
||||||
|
async fn accept(&self, uid: String, activity: serde_json::Value) -> crate::Result<String>;
|
||||||
|
async fn reject(&self, _uid: String, _activity: serde_json::Value) -> crate::Result<String>;
|
||||||
|
async fn undo(&self, uid: String, activity: serde_json::Value) -> crate::Result<String>;
|
||||||
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ pub fn ap_user(user: model::user::Model) -> serde_json::Value {
|
||||||
.set_public_key_pem(&user.public_key)
|
.set_public_key_pem(&user.public_key)
|
||||||
))
|
))
|
||||||
.set_discoverable(Some(true))
|
.set_discoverable(Some(true))
|
||||||
.set_endpoints(None)
|
.set_endpoints(Node::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
||||||
|
|
|
@ -1,8 +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, Order, QueryOrder, QuerySelect};
|
||||||
|
|
||||||
use apb::{AcceptType, Activity, ActivityMut, ActivityType, ObjectMut, Base, BaseMut, BaseType, Node, ObjectType};
|
use apb::{AcceptType, ActivityMut, ActivityType, Base, BaseType, Node, ObjectType, RejectType};
|
||||||
use crate::{activitypub::{jsonld::LD, Addressed, CreationResult, JsonLD, Pagination}, auth::{AuthIdentity, Identity}, errors::UpubError, model, server::Context, url};
|
use crate::{activitypub::{jsonld::LD, APOutbox, 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>,
|
||||||
|
@ -75,219 +75,29 @@ pub async fn post(
|
||||||
Identity::Local(uid) => if ctx.uid(id.clone()) == uid {
|
Identity::Local(uid) => if ctx.uid(id.clone()) == uid {
|
||||||
match activity.base_type() {
|
match activity.base_type() {
|
||||||
None => Err(StatusCode::BAD_REQUEST.into()),
|
None => Err(StatusCode::BAD_REQUEST.into()),
|
||||||
|
|
||||||
Some(BaseType::Link(_)) => Err(StatusCode::UNPROCESSABLE_ENTITY.into()),
|
Some(BaseType::Link(_)) => Err(StatusCode::UNPROCESSABLE_ENTITY.into()),
|
||||||
|
|
||||||
Some(BaseType::Object(ObjectType::Note)) => {
|
Some(BaseType::Object(ObjectType::Note)) =>
|
||||||
let oid = ctx.oid(uuid::Uuid::new_v4().to_string());
|
Ok(CreationResult(ctx.post_note(uid, activity).await?)),
|
||||||
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
|
||||||
let activity_targets = activity.addressed();
|
|
||||||
let object_model = model::object::Model::new(
|
|
||||||
&activity
|
|
||||||
.set_id(Some(&oid))
|
|
||||||
.set_attributed_to(Node::link(uid.clone()))
|
|
||||||
.set_published(Some(chrono::Utc::now()))
|
|
||||||
)?;
|
|
||||||
let activity_model = model::activity::Model {
|
|
||||||
id: aid.clone(),
|
|
||||||
activity_type: ActivityType::Create,
|
|
||||||
actor: uid.clone(),
|
|
||||||
object: Some(oid.clone()),
|
|
||||||
target: None,
|
|
||||||
cc: object_model.cc.clone(),
|
|
||||||
bcc: object_model.bcc.clone(),
|
|
||||||
to: object_model.to.clone(),
|
|
||||||
bto: object_model.bto.clone(),
|
|
||||||
published: object_model.published,
|
|
||||||
};
|
|
||||||
|
|
||||||
model::object::Entity::insert(object_model.into_active_model())
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) =>
|
||||||
.exec(ctx.db()).await?;
|
Ok(CreationResult(ctx.post_activity(uid, activity).await?)),
|
||||||
model::activity::Entity::insert(activity_model.into_active_model())
|
|
||||||
.exec(ctx.db()).await?;
|
|
||||||
|
|
||||||
let addressed = ctx.expand_addressing(&uid, activity_targets).await?;
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) =>
|
||||||
ctx.address_to(&aid, Some(&oid), &addressed).await?;
|
Ok(CreationResult(ctx.like(uid, activity).await?)),
|
||||||
ctx.deliver_to(&aid, &uid, &addressed).await?;
|
|
||||||
|
|
||||||
Ok(CreationResult(aid))
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Follow))) =>
|
||||||
},
|
Ok(CreationResult(ctx.follow(uid, activity).await?)),
|
||||||
|
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) => {
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Undo))) =>
|
||||||
let Some(object) = activity.object().extract() else {
|
Ok(CreationResult(ctx.undo(uid, activity).await?)),
|
||||||
return Err(StatusCode::BAD_REQUEST.into());
|
|
||||||
};
|
|
||||||
let oid = ctx.oid(uuid::Uuid::new_v4().to_string());
|
|
||||||
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
|
||||||
let activity_targets = activity.addressed();
|
|
||||||
let mut object_model = model::object::Model::new(
|
|
||||||
&object
|
|
||||||
.set_id(Some(&oid))
|
|
||||||
.set_attributed_to(Node::link(uid.clone()))
|
|
||||||
.set_published(Some(chrono::Utc::now()))
|
|
||||||
)?;
|
|
||||||
let mut activity_model = model::activity::Model::new(
|
|
||||||
&activity
|
|
||||||
.set_id(Some(&aid))
|
|
||||||
.set_actor(Node::link(uid.clone()))
|
|
||||||
.set_published(Some(chrono::Utc::now()))
|
|
||||||
)?;
|
|
||||||
object_model.to = activity_model.to.clone();
|
|
||||||
object_model.bto = activity_model.bto.clone();
|
|
||||||
object_model.cc = activity_model.cc.clone();
|
|
||||||
object_model.bcc = activity_model.bcc.clone();
|
|
||||||
activity_model.object = Some(oid.clone());
|
|
||||||
|
|
||||||
model::object::Entity::insert(object_model.into_active_model())
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Accept(AcceptType::Accept)))) =>
|
||||||
.exec(ctx.db()).await?;
|
Ok(CreationResult(ctx.accept(uid, activity).await?)),
|
||||||
model::activity::Entity::insert(activity_model.into_active_model())
|
|
||||||
.exec(ctx.db()).await?;
|
|
||||||
|
|
||||||
let addressed = ctx.expand_addressing(&uid, activity_targets).await?;
|
Some(BaseType::Object(ObjectType::Activity(ActivityType::Reject(RejectType::Reject)))) =>
|
||||||
ctx.address_to(&aid, Some(&oid), &addressed).await?;
|
Ok(CreationResult(ctx.reject(uid, activity).await?)),
|
||||||
ctx.deliver_to(&aid, &uid, &addressed).await?;
|
|
||||||
Ok(CreationResult(aid))
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => {
|
|
||||||
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
|
||||||
let activity_targets = activity.addressed();
|
|
||||||
let Some(oid) = activity.object().id() else {
|
|
||||||
return Err(StatusCode::BAD_REQUEST.into());
|
|
||||||
};
|
|
||||||
let activity_model = model::activity::Model::new(
|
|
||||||
&activity
|
|
||||||
.set_id(Some(&aid))
|
|
||||||
.set_published(Some(chrono::Utc::now()))
|
|
||||||
.set_actor(Node::link(uid.clone()))
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let like_model = model::like::ActiveModel {
|
|
||||||
actor: Set(uid.clone()),
|
|
||||||
likes: Set(oid),
|
|
||||||
date: Set(chrono::Utc::now()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
model::like::Entity::insert(like_model).exec(ctx.db()).await?;
|
|
||||||
model::activity::Entity::insert(activity_model.into_active_model())
|
|
||||||
.exec(ctx.db()).await?;
|
|
||||||
|
|
||||||
let addressed = ctx.expand_addressing(&uid, activity_targets).await?;
|
|
||||||
ctx.address_to(&aid, None, &addressed).await?;
|
|
||||||
ctx.deliver_to(&aid, &uid, &addressed).await?;
|
|
||||||
Ok(CreationResult(aid))
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Follow))) => {
|
|
||||||
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
|
||||||
let activity_targets = activity.addressed();
|
|
||||||
if activity.object().id().is_none() {
|
|
||||||
return Err(StatusCode::BAD_REQUEST.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let activity_model = model::activity::Model::new(
|
|
||||||
&activity
|
|
||||||
.set_id(Some(&aid))
|
|
||||||
.set_actor(Node::link(uid.clone()))
|
|
||||||
.set_published(Some(chrono::Utc::now()))
|
|
||||||
)?;
|
|
||||||
model::activity::Entity::insert(activity_model.into_active_model())
|
|
||||||
.exec(ctx.db()).await?;
|
|
||||||
|
|
||||||
let addressed = ctx.expand_addressing(&uid, activity_targets).await?;
|
|
||||||
ctx.address_to(&aid, None, &addressed).await?;
|
|
||||||
ctx.deliver_to(&aid, &uid, &addressed).await?;
|
|
||||||
Ok(CreationResult(aid))
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Undo))) => {
|
|
||||||
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
|
||||||
let activity_targets = activity.addressed();
|
|
||||||
{
|
|
||||||
let Some(old_aid) = activity.object().id() else {
|
|
||||||
return Err(StatusCode::BAD_REQUEST.into());
|
|
||||||
};
|
|
||||||
let Some(old_activity) = model::activity::Entity::find_by_id(old_aid)
|
|
||||||
.one(ctx.db()).await?
|
|
||||||
else {
|
|
||||||
return Err(StatusCode::NOT_FOUND.into());
|
|
||||||
};
|
|
||||||
if old_activity.actor != uid {
|
|
||||||
return Err(StatusCode::FORBIDDEN.into());
|
|
||||||
}
|
|
||||||
match old_activity.activity_type {
|
|
||||||
ActivityType::Like => {
|
|
||||||
model::like::Entity::delete(model::like::ActiveModel {
|
|
||||||
actor: Set(old_activity.actor), likes: Set(old_activity.object.unwrap_or("".into())),
|
|
||||||
..Default::default()
|
|
||||||
}).exec(ctx.db()).await?;
|
|
||||||
},
|
|
||||||
ActivityType::Follow => {
|
|
||||||
model::relation::Entity::delete(model::relation::ActiveModel {
|
|
||||||
follower: Set(old_activity.actor), following: Set(old_activity.object.unwrap_or("".into())),
|
|
||||||
..Default::default()
|
|
||||||
}).exec(ctx.db()).await?;
|
|
||||||
},
|
|
||||||
t => tracing::warn!("extra side effects for activity {t:?} not implemented"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let activity_model = model::activity::Model::new(
|
|
||||||
&activity
|
|
||||||
.set_id(Some(&aid))
|
|
||||||
.set_actor(Node::link(uid.clone()))
|
|
||||||
.set_published(Some(chrono::Utc::now()))
|
|
||||||
)?;
|
|
||||||
model::activity::Entity::insert(activity_model.into_active_model()).exec(ctx.db()).await?;
|
|
||||||
|
|
||||||
let addressed = ctx.expand_addressing(&uid, activity_targets).await?;
|
|
||||||
ctx.address_to(&aid, None, &addressed).await?;
|
|
||||||
ctx.deliver_to(&aid, &uid, &addressed).await?;
|
|
||||||
Ok(CreationResult(aid))
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(BaseType::Object(ObjectType::Activity(ActivityType::Accept(AcceptType::Accept)))) => {
|
|
||||||
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
|
||||||
let activity_targets = activity.addressed();
|
|
||||||
if activity.object().id().is_none() {
|
|
||||||
return Err(StatusCode::BAD_REQUEST.into());
|
|
||||||
}
|
|
||||||
let Some(accepted_id) = activity.object().id() else {
|
|
||||||
return Err(StatusCode::BAD_REQUEST.into());
|
|
||||||
};
|
|
||||||
let Some(accepted_activity) = model::activity::Entity::find_by_id(accepted_id)
|
|
||||||
.one(ctx.db()).await?
|
|
||||||
else {
|
|
||||||
return Err(StatusCode::NOT_FOUND.into());
|
|
||||||
};
|
|
||||||
|
|
||||||
match accepted_activity.activity_type {
|
|
||||||
ActivityType::Follow => {
|
|
||||||
model::relation::Entity::insert(
|
|
||||||
model::relation::ActiveModel {
|
|
||||||
follower: Set(accepted_activity.actor), following: Set(uid.clone()),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
).exec(ctx.db()).await?;
|
|
||||||
},
|
|
||||||
t => tracing::warn!("no side effects implemented for accepting {t:?}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
let activity_model = model::activity::Model::new(
|
|
||||||
&activity
|
|
||||||
.set_id(Some(&aid))
|
|
||||||
.set_actor(Node::link(uid.clone()))
|
|
||||||
.set_published(Some(chrono::Utc::now()))
|
|
||||||
)?;
|
|
||||||
model::activity::Entity::insert(activity_model.into_active_model())
|
|
||||||
.exec(ctx.db()).await?;
|
|
||||||
|
|
||||||
let addressed = ctx.expand_addressing(&uid, activity_targets).await?;
|
|
||||||
ctx.address_to(&aid, None, &addressed).await?;
|
|
||||||
ctx.deliver_to(&aid, &uid, &addressed).await?;
|
|
||||||
Ok(CreationResult(aid))
|
|
||||||
},
|
|
||||||
|
|
||||||
// Some(BaseType::Object(ObjectType::Activity(ActivityType::Reject(RejectType::Reject)))) => {
|
|
||||||
// },
|
|
||||||
|
|
||||||
Some(_) => Err(StatusCode::NOT_IMPLEMENTED.into()),
|
Some(_) => Err(StatusCode::NOT_IMPLEMENTED.into()),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum UpubError {
|
pub enum UpubError {
|
||||||
#[error("database error: {0}")]
|
#[error("database error: {0}")]
|
||||||
|
@ -17,8 +19,8 @@ pub enum UpubError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpubError {
|
impl UpubError {
|
||||||
pub fn code(code: axum::http::StatusCode) -> Self {
|
pub fn bad_request() -> Self {
|
||||||
UpubError::Status(code)
|
Self::Status(StatusCode::BAD_REQUEST)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
231
src/server.rs
231
src/server.rs
|
@ -1,10 +1,11 @@
|
||||||
use std::{str::Utf8Error, sync::Arc};
|
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 reqwest::StatusCode;
|
||||||
|
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, DbErr, EntityTrait, IntoActiveModel, QueryFilter, QuerySelect, SelectColumns, Set};
|
||||||
|
|
||||||
use crate::{activitypub::{jsonld::LD, PUBLIC_TARGET}, dispatcher::Dispatcher, fetcher::Fetcher, model};
|
use crate::{activitypub::{jsonld::LD, APOutbox, Addressed, CreationResult, PUBLIC_TARGET}, dispatcher::Dispatcher, errors::UpubError, fetcher::Fetcher, model};
|
||||||
use apb::{CollectionPageMut, CollectionMut, CollectionType, BaseMut, Node};
|
use apb::{Activity, ActivityMut, BaseMut, CollectionMut, CollectionPageMut, CollectionType, Node, ObjectMut};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Context(Arc<ContextInner>);
|
pub struct Context(Arc<ContextInner>);
|
||||||
|
@ -38,6 +39,8 @@ pub enum ContextError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
|
||||||
|
// TODO slim constructor down, maybe make a builder?
|
||||||
pub async fn new(db: DatabaseConnection, mut domain: String) -> Result<Self, ContextError> {
|
pub async fn new(db: DatabaseConnection, mut domain: String) -> Result<Self, ContextError> {
|
||||||
let protocol = if domain.starts_with("http://")
|
let protocol = if domain.starts_with("http://")
|
||||||
{ "http://" } else { "https://" }.to_string();
|
{ "http://" } else { "https://" }.to_string();
|
||||||
|
@ -208,6 +211,7 @@ impl Context {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO should probs not be here
|
||||||
pub fn ap_collection(&self, id: &str, total_items: Option<u64>) -> serde_json::Value {
|
pub fn ap_collection(&self, id: &str, total_items: Option<u64>) -> serde_json::Value {
|
||||||
serde_json::Value::new_object()
|
serde_json::Value::new_object()
|
||||||
.set_id(Some(id))
|
.set_id(Some(id))
|
||||||
|
@ -216,6 +220,7 @@ impl Context {
|
||||||
.set_total_items(total_items)
|
.set_total_items(total_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO should probs not be here
|
||||||
pub fn ap_collection_page(&self, id: &str, offset: u64, limit: u64, items: Vec<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}")))
|
||||||
|
@ -224,4 +229,224 @@ impl Context {
|
||||||
.set_next(Node::link(format!("{id}?offset={}", offset+limit)))
|
.set_next(Node::link(format!("{id}?offset={}", offset+limit)))
|
||||||
.set_ordered_items(Node::Array(items))
|
.set_ordered_items(Node::Array(items))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn dispatch(&self, uid: &str, activity_targets: Vec<String>, aid: &str, oid: Option<&str>) -> crate::Result<()> {
|
||||||
|
let addressed = self.expand_addressing(uid, activity_targets).await?;
|
||||||
|
self.address_to(aid, oid, &addressed).await?;
|
||||||
|
self.deliver_to(aid, uid, &addressed).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::async_trait]
|
||||||
|
impl APOutbox for Context {
|
||||||
|
async fn post_note(&self, uid: String, object: serde_json::Value) -> crate::Result<String> {
|
||||||
|
let oid = self.oid(uuid::Uuid::new_v4().to_string());
|
||||||
|
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
||||||
|
let activity_targets = object.addressed();
|
||||||
|
let object_model = model::object::Model::new(
|
||||||
|
&object
|
||||||
|
.set_id(Some(&oid))
|
||||||
|
.set_attributed_to(Node::link(uid.clone()))
|
||||||
|
.set_published(Some(chrono::Utc::now()))
|
||||||
|
)?;
|
||||||
|
let activity_model = model::activity::Model {
|
||||||
|
id: aid.clone(),
|
||||||
|
activity_type: apb::ActivityType::Create,
|
||||||
|
actor: uid.clone(),
|
||||||
|
object: Some(oid.clone()),
|
||||||
|
target: None,
|
||||||
|
cc: object_model.cc.clone(),
|
||||||
|
bcc: object_model.bcc.clone(),
|
||||||
|
to: object_model.to.clone(),
|
||||||
|
bto: object_model.bto.clone(),
|
||||||
|
published: object_model.published,
|
||||||
|
};
|
||||||
|
|
||||||
|
model::object::Entity::insert(object_model.into_active_model())
|
||||||
|
.exec(self.db()).await?;
|
||||||
|
model::activity::Entity::insert(activity_model.into_active_model())
|
||||||
|
.exec(self.db()).await?;
|
||||||
|
|
||||||
|
self.dispatch(&uid, activity_targets, &aid, Some(&oid)).await?;
|
||||||
|
|
||||||
|
Ok(aid)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_activity(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
|
let Some(object) = activity.object().extract() else {
|
||||||
|
return Err(UpubError::bad_request());
|
||||||
|
};
|
||||||
|
|
||||||
|
let oid = self.oid(uuid::Uuid::new_v4().to_string());
|
||||||
|
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
||||||
|
let activity_targets = activity.addressed();
|
||||||
|
let mut object_model = model::object::Model::new(
|
||||||
|
&object
|
||||||
|
.set_id(Some(&oid))
|
||||||
|
.set_attributed_to(Node::link(uid.clone()))
|
||||||
|
.set_published(Some(chrono::Utc::now()))
|
||||||
|
)?;
|
||||||
|
let mut activity_model = model::activity::Model::new(
|
||||||
|
&activity
|
||||||
|
.set_id(Some(&aid))
|
||||||
|
.set_actor(Node::link(uid.clone()))
|
||||||
|
.set_published(Some(chrono::Utc::now()))
|
||||||
|
)?;
|
||||||
|
object_model.to = activity_model.to.clone();
|
||||||
|
object_model.bto = activity_model.bto.clone();
|
||||||
|
object_model.cc = activity_model.cc.clone();
|
||||||
|
object_model.bcc = activity_model.bcc.clone();
|
||||||
|
activity_model.object = Some(oid.clone());
|
||||||
|
|
||||||
|
model::object::Entity::insert(object_model.into_active_model())
|
||||||
|
.exec(self.db()).await?;
|
||||||
|
model::activity::Entity::insert(activity_model.into_active_model())
|
||||||
|
.exec(self.db()).await?;
|
||||||
|
|
||||||
|
self.dispatch(&uid, activity_targets, &aid, Some(&oid)).await?;
|
||||||
|
|
||||||
|
Ok(aid)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn like(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
|
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
||||||
|
let activity_targets = activity.addressed();
|
||||||
|
let Some(oid) = activity.object().id() else {
|
||||||
|
return Err(StatusCode::BAD_REQUEST.into());
|
||||||
|
};
|
||||||
|
let activity_model = model::activity::Model::new(
|
||||||
|
&activity
|
||||||
|
.set_id(Some(&aid))
|
||||||
|
.set_published(Some(chrono::Utc::now()))
|
||||||
|
.set_actor(Node::link(uid.clone()))
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let like_model = model::like::ActiveModel {
|
||||||
|
actor: Set(uid.clone()),
|
||||||
|
likes: Set(oid),
|
||||||
|
date: Set(chrono::Utc::now()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
model::like::Entity::insert(like_model).exec(self.db()).await?;
|
||||||
|
model::activity::Entity::insert(activity_model.into_active_model())
|
||||||
|
.exec(self.db()).await?;
|
||||||
|
|
||||||
|
self.dispatch(&uid, activity_targets, &aid, None).await?;
|
||||||
|
|
||||||
|
Ok(aid)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn follow(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
|
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
||||||
|
let activity_targets = activity.addressed();
|
||||||
|
if activity.object().id().is_none() {
|
||||||
|
return Err(StatusCode::BAD_REQUEST.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let activity_model = model::activity::Model::new(
|
||||||
|
&activity
|
||||||
|
.set_id(Some(&aid))
|
||||||
|
.set_actor(Node::link(uid.clone()))
|
||||||
|
.set_published(Some(chrono::Utc::now()))
|
||||||
|
)?;
|
||||||
|
model::activity::Entity::insert(activity_model.into_active_model())
|
||||||
|
.exec(self.db()).await?;
|
||||||
|
|
||||||
|
self.dispatch(&uid, activity_targets, &aid, None).await?;
|
||||||
|
|
||||||
|
Ok(aid)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn accept(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
|
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
||||||
|
let activity_targets = activity.addressed();
|
||||||
|
if activity.object().id().is_none() {
|
||||||
|
return Err(StatusCode::BAD_REQUEST.into());
|
||||||
|
}
|
||||||
|
let Some(accepted_id) = activity.object().id() else {
|
||||||
|
return Err(StatusCode::BAD_REQUEST.into());
|
||||||
|
};
|
||||||
|
let Some(accepted_activity) = model::activity::Entity::find_by_id(accepted_id)
|
||||||
|
.one(self.db()).await?
|
||||||
|
else {
|
||||||
|
return Err(StatusCode::NOT_FOUND.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
match accepted_activity.activity_type {
|
||||||
|
apb::ActivityType::Follow => {
|
||||||
|
model::relation::Entity::insert(
|
||||||
|
model::relation::ActiveModel {
|
||||||
|
follower: Set(accepted_activity.actor), following: Set(uid.clone()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
).exec(self.db()).await?;
|
||||||
|
},
|
||||||
|
t => tracing::warn!("no side effects implemented for accepting {t:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let activity_model = model::activity::Model::new(
|
||||||
|
&activity
|
||||||
|
.set_id(Some(&aid))
|
||||||
|
.set_actor(Node::link(uid.clone()))
|
||||||
|
.set_published(Some(chrono::Utc::now()))
|
||||||
|
)?;
|
||||||
|
model::activity::Entity::insert(activity_model.into_active_model())
|
||||||
|
.exec(self.db()).await?;
|
||||||
|
|
||||||
|
self.dispatch(&uid, activity_targets, &aid, None).await?;
|
||||||
|
|
||||||
|
Ok(aid)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reject(&self, _uid: String, _activity: serde_json::Value) -> crate::Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn undo(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
|
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
||||||
|
let activity_targets = activity.addressed();
|
||||||
|
{
|
||||||
|
let Some(old_aid) = activity.object().id() else {
|
||||||
|
return Err(StatusCode::BAD_REQUEST.into());
|
||||||
|
};
|
||||||
|
let Some(old_activity) = model::activity::Entity::find_by_id(old_aid)
|
||||||
|
.one(self.db()).await?
|
||||||
|
else {
|
||||||
|
return Err(StatusCode::NOT_FOUND.into());
|
||||||
|
};
|
||||||
|
if old_activity.actor != uid {
|
||||||
|
return Err(StatusCode::FORBIDDEN.into());
|
||||||
|
}
|
||||||
|
match old_activity.activity_type {
|
||||||
|
apb::ActivityType::Like => {
|
||||||
|
model::like::Entity::delete(model::like::ActiveModel {
|
||||||
|
actor: Set(old_activity.actor), likes: Set(old_activity.object.unwrap_or("".into())),
|
||||||
|
..Default::default()
|
||||||
|
}).exec(self.db()).await?;
|
||||||
|
},
|
||||||
|
apb::ActivityType::Follow => {
|
||||||
|
model::relation::Entity::delete(model::relation::ActiveModel {
|
||||||
|
follower: Set(old_activity.actor), following: Set(old_activity.object.unwrap_or("".into())),
|
||||||
|
..Default::default()
|
||||||
|
}).exec(self.db()).await?;
|
||||||
|
},
|
||||||
|
t => tracing::warn!("extra side effects for activity {t:?} not implemented"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let activity_model = model::activity::Model::new(
|
||||||
|
&activity
|
||||||
|
.set_id(Some(&aid))
|
||||||
|
.set_actor(Node::link(uid.clone()))
|
||||||
|
.set_published(Some(chrono::Utc::now()))
|
||||||
|
)?;
|
||||||
|
model::activity::Entity::insert(activity_model.into_active_model())
|
||||||
|
.exec(self.db())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
self.dispatch(&uid, activity_targets, &aid, None).await?;
|
||||||
|
|
||||||
|
Ok(aid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue