diff --git a/src/activitypub/activity.rs b/src/activitypub/activity.rs new file mode 100644 index 00000000..b71a4dc1 --- /dev/null +++ b/src/activitypub/activity.rs @@ -0,0 +1,20 @@ +use std::{ops::Deref, sync::Arc}; + +use axum::{extract::{Path, State}, http::StatusCode, Json}; +use sea_orm::{DatabaseConnection, EntityTrait}; + +use crate::{activitystream::Base, model::activity}; + + +pub async fn view(State(db) : State>, Path(id): Path) -> Result, StatusCode> { + let uri = format!("http://localhost:3000/activities/{id}"); + match activity::Entity::find_by_id(uri).one(db.deref()).await { + Ok(Some(activity)) => Ok(Json(activity.underlying_json_object())), + Ok(None) => Err(StatusCode::NOT_FOUND), + Err(e) => { + tracing::error!("error querying for activity: {e}"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + }, + } +} + diff --git a/src/activitypub/mod.rs b/src/activitypub/mod.rs new file mode 100644 index 00000000..1f6e6c05 --- /dev/null +++ b/src/activitypub/mod.rs @@ -0,0 +1,50 @@ +pub mod user; +pub mod object; +pub mod activity; + +use std::{ops::Deref, sync::Arc}; + +use axum::{extract::State, http::StatusCode, Json}; +use sea_orm::{DatabaseConnection, EntityTrait, IntoActiveModel}; + +use crate::{activitystream::{object::{ObjectType, activity::{Activity, ActivityType}}, Base, BaseType, Node}, model}; + + +pub fn uri_id(id: String) -> String { + if id.starts_with("http") { id } else { format!("http://localhost:3000/users/{id}") } +} + +pub async fn inbox(State(db) : State>, Json(object): Json) -> Result, StatusCode> { + match object.base_type() { + None => { Err(StatusCode::BAD_REQUEST) }, + Some(BaseType::Link(_x)) => Err(StatusCode::UNPROCESSABLE_ENTITY), // we could but not yet + Some(BaseType::Object(ObjectType::Activity(ActivityType::Activity))) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Some(BaseType::Object(ObjectType::Activity(ActivityType::Follow))) => { todo!() }, + Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => { todo!() }, + Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) => { + let Ok(activity_entity) = model::activity::Model::new(&object) else { + return Err(StatusCode::UNPROCESSABLE_ENTITY); + }; + let Node::Object(obj) = object.object() else { + // TODO we could process non-embedded activities or arrays but im lazy rn + return Err(StatusCode::UNPROCESSABLE_ENTITY); + }; + let Ok(obj_entity) = model::object::Model::new(&*obj) else { + return Err(StatusCode::UNPROCESSABLE_ENTITY); + }; + model::object::Entity::insert(obj_entity.into_active_model()) + .exec(db.deref()) + .await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + model::activity::Entity::insert(activity_entity.into_active_model()) + .exec(db.deref()) + .await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + Ok(Json(serde_json::Value::Null)) // TODO hmmmmmmmmmmm not the best value to return.... + }, + Some(BaseType::Object(ObjectType::Activity(_x))) => { Err(StatusCode::NOT_IMPLEMENTED) }, + Some(_x) => { Err(StatusCode::UNPROCESSABLE_ENTITY) } + } +} + +pub async fn outbox(State(_db): State>) -> Result, StatusCode> { + todo!() +} diff --git a/src/activitypub/object.rs b/src/activitypub/object.rs new file mode 100644 index 00000000..f735c97c --- /dev/null +++ b/src/activitypub/object.rs @@ -0,0 +1,19 @@ +use std::{ops::Deref, sync::Arc}; + +use axum::{extract::{Path, State}, http::StatusCode, Json}; +use sea_orm::{DatabaseConnection, EntityTrait}; + +use crate::{activitystream::Base, model::object}; + + +pub async fn view(State(db) : State>, Path(id): Path) -> Result, StatusCode> { + let uri = format!("http://localhost:3000/objects/{id}"); + match object::Entity::find_by_id(uri).one(db.deref()).await { + Ok(Some(object)) => Ok(Json(object.underlying_json_object())), + Ok(None) => Err(StatusCode::NOT_FOUND), + Err(e) => { + tracing::error!("error querying for object: {e}"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + }, + } +} diff --git a/src/activitypub/user.rs b/src/activitypub/user.rs new file mode 100644 index 00000000..baea0619 --- /dev/null +++ b/src/activitypub/user.rs @@ -0,0 +1,67 @@ +use std::sync::Arc; + +use axum::{extract::{Path, State}, http::StatusCode, Json}; +use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, IntoActiveModel, QueryFilter}; + +use crate::{activitystream::{object::{activity::{Activity, ActivityType}, ObjectType}, Base, BaseType, Node}, model::{activity, object, user}}; + +pub async fn list(State(_db) : State>) -> Result, StatusCode> { + todo!() +} + +pub async fn view(State(db) : State>, Path(id): Path) -> Result, StatusCode> { + match user::Entity::find_by_id(super::uri_id(id)).one(&*db).await { + Ok(Some(user)) => Ok(Json(user.underlying_json_object())), + Ok(None) => Err(StatusCode::NOT_FOUND), + Err(e) => { + tracing::error!("error querying for user: {e}"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + }, + } +} + +pub async fn outbox(State(db): State>, Path(id): Path) -> Result, StatusCode> { + let uri = super::uri_id(id); + match activity::Entity::find() + .filter(Condition::all().add(activity::Column::Actor.eq(uri))) + .all(&*db).await + { + Ok(_x) => todo!(), + Err(_e) => todo!(), + } +} + +pub async fn inbox( + State(db): State>, + Path(_id): Path, + Json(object): Json +) -> Result, StatusCode> { + match object.base_type() { + None => { Err(StatusCode::BAD_REQUEST) }, + Some(BaseType::Link(_x)) => Err(StatusCode::UNPROCESSABLE_ENTITY), // we could but not yet + Some(BaseType::Object(ObjectType::Activity(ActivityType::Activity))) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Some(BaseType::Object(ObjectType::Activity(ActivityType::Follow))) => { todo!() }, + Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => { todo!() }, + Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) => { + let Ok(activity_entity) = activity::Model::new(&object) else { + return Err(StatusCode::UNPROCESSABLE_ENTITY); + }; + let Node::Object(obj) = object.object() else { + // TODO we could process non-embedded activities or arrays but im lazy rn + return Err(StatusCode::UNPROCESSABLE_ENTITY); + }; + let Ok(obj_entity) = object::Model::new(&*obj) else { + return Err(StatusCode::UNPROCESSABLE_ENTITY); + }; + object::Entity::insert(obj_entity.into_active_model()) + .exec(&*db) + .await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + activity::Entity::insert(activity_entity.into_active_model()) + .exec(&*db) + .await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + Ok(Json(serde_json::Value::Null)) // TODO hmmmmmmmmmmm not the best value to return.... + }, + Some(BaseType::Object(ObjectType::Activity(_x))) => { Err(StatusCode::NOT_IMPLEMENTED) }, + Some(_x) => { Err(StatusCode::UNPROCESSABLE_ENTITY) } + } +} diff --git a/src/server.rs b/src/server.rs index e848b144..a5328a59 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,20 +1,22 @@ -use std::ops::Deref; use std::sync::Arc; -use crate::activitystream::object::{Activity, ActivityType}; -use crate::activitystream::{Base, BaseType, Node, ObjectType}; -use crate::model::{activity, object, user, ToJson}; -use axum::{extract::{Path, State}, http::StatusCode, routing::{get, post}, Json, Router}; -use sea_orm::{DatabaseConnection, EntityTrait, IntoActiveModel}; +use axum::{routing::{get, post}, Router}; +use sea_orm::DatabaseConnection; +use crate::activitypub as ap; pub async fn serve(db: DatabaseConnection) { // build our application with a single route let app = Router::new() - .route("/inbox", post(inbox)) - .route("/outbox", get(|| async { todo!() })) - .route("/users/:id", get(user)) - .route("/activities/:id", get(activity)) - .route("/objects/:id", get(object)) + // core server inbox/outbox, maybe for feeds? TODO do we need these? + .route("/inbox", post(ap::inbox)) + .route("/outbox", get(ap::outbox)) + // actor routes + .route("/users/:id", get(ap::user::view)) + .route("/users/:id/inbox", post(ap::user::inbox)) + .route("/users/:id/outbox", get(ap::user::outbox)) + // specific object routes + .route("/activities/:id", get(ap::activity::view)) + .route("/objects/:id", get(ap::object::view)) .with_state(Arc::new(db)); // run our app with hyper, listening globally on port 3000 @@ -24,70 +26,3 @@ pub async fn serve(db: DatabaseConnection) { .await .unwrap(); } - -async fn inbox(State(db) : State>, Json(object): Json) -> Result, StatusCode> { - match object.base_type() { - None => { Err(StatusCode::BAD_REQUEST) }, - Some(BaseType::Link(_x)) => Err(StatusCode::UNPROCESSABLE_ENTITY), // we could but not yet - Some(BaseType::Object(ObjectType::Activity(ActivityType::Activity))) => Err(StatusCode::UNPROCESSABLE_ENTITY), - Some(BaseType::Object(ObjectType::Activity(ActivityType::Follow))) => { todo!() }, - Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => { todo!() }, - Some(BaseType::Object(ObjectType::Activity(ActivityType::Create))) => { - let Ok(activity_entity) = activity::Model::new(&object) else { - return Err(StatusCode::UNPROCESSABLE_ENTITY); - }; - let Node::Object(obj) = object.object() else { - // TODO we could process non-embedded activities or arrays but im lazy rn - return Err(StatusCode::UNPROCESSABLE_ENTITY); - }; - let Ok(obj_entity) = object::Model::new(&obj) else { - return Err(StatusCode::UNPROCESSABLE_ENTITY); - }; - object::Entity::insert(obj_entity.into_active_model()) - .exec(db.deref()) - .await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - activity::Entity::insert(activity_entity.into_active_model()) - .exec(db.deref()) - .await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - Ok(Json(serde_json::Value::Null)) // TODO hmmmmmmmmmmm not the best value to return.... - }, - Some(BaseType::Object(ObjectType::Activity(_x))) => { Err(StatusCode::NOT_IMPLEMENTED) }, - Some(_x) => { Err(StatusCode::UNPROCESSABLE_ENTITY) } - } -} - -async fn user(State(db) : State>, Path(id): Path) -> Result, StatusCode> { - let uri = format!("http://localhost:3000/users/{id}"); - match user::Entity::find_by_id(uri).one(db.deref()).await { - Ok(Some(user)) => Ok(Json(user.json())), - Ok(None) => Err(StatusCode::NOT_FOUND), - Err(e) => { - tracing::error!("error querying for user: {e}"); - Err(StatusCode::INTERNAL_SERVER_ERROR) - }, - } -} - -async fn activity(State(db) : State>, Path(id): Path) -> Result, StatusCode> { - let uri = format!("http://localhost:3000/activities/{id}"); - match activity::Entity::find_by_id(uri).one(db.deref()).await { - Ok(Some(activity)) => Ok(Json(activity.json())), - Ok(None) => Err(StatusCode::NOT_FOUND), - Err(e) => { - tracing::error!("error querying for activity: {e}"); - Err(StatusCode::INTERNAL_SERVER_ERROR) - }, - } -} - -async fn object(State(db) : State>, Path(id): Path) -> Result, StatusCode> { - let uri = format!("http://localhost:3000/objects/{id}"); - match object::Entity::find_by_id(uri).one(db.deref()).await { - Ok(Some(object)) => Ok(Json(object.json())), - Ok(None) => Err(StatusCode::NOT_FOUND), - Err(e) => { - tracing::error!("error querying for object: {e}"); - Err(StatusCode::INTERNAL_SERVER_ERROR) - }, - } -}