forked from alemi/upub
chore: updated apb things, restructured a bit
This commit is contained in:
parent
a9229adec8
commit
86e84d88aa
15 changed files with 237 additions and 288 deletions
|
@ -99,7 +99,13 @@ async fn main() {
|
||||||
let ctx = server::Context::new(db, args.domain)
|
let ctx = server::Context::new(db, args.domain)
|
||||||
.await.expect("failed creating server context");
|
.await.expect("failed creating server context");
|
||||||
|
|
||||||
let router = routes::activitypub::router().with_state(ctx);
|
use routes::activitypub::ActivityPubRouter;
|
||||||
|
use routes::mastodon::MastodonRouter;
|
||||||
|
|
||||||
|
let router = axum::Router::new()
|
||||||
|
.ap_routes()
|
||||||
|
.mastodon_routes() // no-op if mastodon feature is disabled
|
||||||
|
.with_state(ctx);
|
||||||
|
|
||||||
// run our app with hyper, listening locally on port 3000
|
// run our app with hyper, listening locally on port 3000
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||||
|
@ -113,7 +119,6 @@ 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 apb::{Base, Object};
|
use apb::{Base, Object};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{routes::activitypub::PUBLIC_TARGET, model::{config, credential}};
|
use crate::model::{config, credential};
|
||||||
use super::{activity, object, user, Audience};
|
use super::{activity, object, user, Audience};
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use sea_orm::IntoActiveModel;
|
use sea_orm::IntoActiveModel;
|
||||||
|
@ -10,7 +10,7 @@ pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String, count: u64)
|
||||||
let test_user = super::user::Model {
|
let test_user = super::user::Model {
|
||||||
id: format!("{domain}/users/test"),
|
id: format!("{domain}/users/test"),
|
||||||
name: Some("μpub".into()),
|
name: Some("μpub".into()),
|
||||||
domain: crate::routes::activitypub::domain(&domain),
|
domain: clean_domain(&domain),
|
||||||
preferred_username: "test".to_string(),
|
preferred_username: "test".to_string(),
|
||||||
summary: Some("hello world! i'm manually generated but served dynamically from db! check progress at https://git.alemi.dev/upub.git".to_string()),
|
summary: Some("hello world! i'm manually generated but served dynamically from db! check progress at https://git.alemi.dev/upub.git".to_string()),
|
||||||
following: None,
|
following: None,
|
||||||
|
@ -64,7 +64,7 @@ pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String, count: u64)
|
||||||
comments: Set(0),
|
comments: Set(0),
|
||||||
likes: Set(0),
|
likes: Set(0),
|
||||||
shares: Set(0),
|
shares: Set(0),
|
||||||
to: Set(Audience(vec![PUBLIC_TARGET.to_string()])),
|
to: Set(Audience(vec![apb::target::PUBLIC.to_string()])),
|
||||||
bto: Set(Audience::default()),
|
bto: Set(Audience::default()),
|
||||||
cc: Set(Audience(vec![])),
|
cc: Set(Audience(vec![])),
|
||||||
bcc: Set(Audience::default()),
|
bcc: Set(Audience::default()),
|
||||||
|
@ -77,7 +77,7 @@ pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String, count: u64)
|
||||||
object: Set(Some(format!("{domain}/objects/{oid}"))),
|
object: Set(Some(format!("{domain}/objects/{oid}"))),
|
||||||
target: Set(None),
|
target: Set(None),
|
||||||
published: Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)),
|
published: Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)),
|
||||||
to: Set(Audience(vec![PUBLIC_TARGET.to_string()])),
|
to: Set(Audience(vec![apb::target::PUBLIC.to_string()])),
|
||||||
bto: Set(Audience::default()),
|
bto: Set(Audience::default()),
|
||||||
cc: Set(Audience(vec![])),
|
cc: Set(Audience(vec![])),
|
||||||
bcc: Set(Audience::default()),
|
bcc: Set(Audience::default()),
|
||||||
|
@ -86,3 +86,10 @@ pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String, count: u64)
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clean_domain(domain: &str) -> String {
|
||||||
|
domain
|
||||||
|
.replace("http://", "")
|
||||||
|
.replace("https://", "")
|
||||||
|
.replace('/', "")
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
use apb::{Collection, Actor, PublicKey, ActorType};
|
use apb::{Collection, Actor, PublicKey, ActorType};
|
||||||
use crate::routes::activitypub;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "users")]
|
#[sea_orm(table_name = "users")]
|
||||||
|
@ -40,7 +39,7 @@ pub struct Model {
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn new(object: &impl Actor) -> Result<Self, super::FieldError> {
|
pub fn new(object: &impl Actor) -> Result<Self, super::FieldError> {
|
||||||
let ap_id = object.id().ok_or(super::FieldError("id"))?.to_string();
|
let ap_id = object.id().ok_or(super::FieldError("id"))?.to_string();
|
||||||
let (domain, preferred_username) = activitypub::split_id(&ap_id);
|
let (domain, preferred_username) = split_user_id(&ap_id);
|
||||||
Ok(Model {
|
Ok(Model {
|
||||||
id: ap_id, preferred_username, domain,
|
id: ap_id, preferred_username, domain,
|
||||||
actor_type: object.actor_type().ok_or(super::FieldError("type"))?,
|
actor_type: object.actor_type().ok_or(super::FieldError("type"))?,
|
||||||
|
@ -121,3 +120,13 @@ impl Related<super::addressing::Entity> for Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
fn split_user_id(id: &str) -> (String, String) {
|
||||||
|
let clean = id
|
||||||
|
.replace("http://", "")
|
||||||
|
.replace("https://", "");
|
||||||
|
let mut splits = clean.split('/');
|
||||||
|
let first = splits.next().unwrap_or("");
|
||||||
|
let last = splits.last().unwrap_or(first);
|
||||||
|
(first.to_string(), last.to_string())
|
||||||
|
}
|
||||||
|
|
25
src/routes/activitypub/application.rs
Normal file
25
src/routes/activitypub/application.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use apb::{ActorMut, BaseMut, ObjectMut, PublicKeyMut};
|
||||||
|
use axum::{extract::State, http::StatusCode, Json};
|
||||||
|
|
||||||
|
use crate::{server::Context, url};
|
||||||
|
|
||||||
|
use super::jsonld::LD;
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn view(State(ctx): State<Context>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||||
|
Ok(Json(
|
||||||
|
serde_json::Value::new_object()
|
||||||
|
.set_id(Some(&url!(ctx, "")))
|
||||||
|
.set_actor_type(Some(apb::ActorType::Application))
|
||||||
|
.set_name(Some("μpub"))
|
||||||
|
.set_summary(Some("micro social network, federated"))
|
||||||
|
.set_published(Some(ctx.app().created))
|
||||||
|
.set_public_key(apb::Node::object(
|
||||||
|
serde_json::Value::new_object()
|
||||||
|
.set_id(Some(&url!(ctx, "#main-key")))
|
||||||
|
.set_owner(Some(&url!(ctx, "")))
|
||||||
|
.set_public_key_pem(&ctx.app().public_key)
|
||||||
|
))
|
||||||
|
.ld_context()
|
||||||
|
))
|
||||||
|
}
|
48
src/routes/activitypub/auth.rs
Normal file
48
src/routes/activitypub/auth.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use axum::{http::StatusCode, extract::State, Json};
|
||||||
|
use rand::Rng;
|
||||||
|
use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter};
|
||||||
|
|
||||||
|
use crate::{model, server::Context};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize)]
|
||||||
|
pub struct LoginForm {
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login(State(ctx): State<Context>, Json(login): Json<LoginForm>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||||
|
// TODO salt the pwd
|
||||||
|
match model::credential::Entity::find()
|
||||||
|
.filter(Condition::all()
|
||||||
|
.add(model::credential::Column::Email.eq(login.email))
|
||||||
|
.add(model::credential::Column::Password.eq(sha256::digest(login.password)))
|
||||||
|
)
|
||||||
|
.one(ctx.db())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Some(x)) => {
|
||||||
|
// TODO should probably use crypto-safe rng
|
||||||
|
let token : String = rand::thread_rng()
|
||||||
|
.sample_iter(&rand::distributions::Alphanumeric)
|
||||||
|
.take(128)
|
||||||
|
.map(char::from)
|
||||||
|
.collect();
|
||||||
|
model::session::Entity::insert(
|
||||||
|
model::session::ActiveModel {
|
||||||
|
id: sea_orm::ActiveValue::Set(token.clone()),
|
||||||
|
actor: sea_orm::ActiveValue::Set(x.id),
|
||||||
|
expires: sea_orm::ActiveValue::Set(chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.exec(ctx.db())
|
||||||
|
.await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
Ok(Json(serde_json::Value::String(token)))
|
||||||
|
},
|
||||||
|
Ok(None) => Err(StatusCode::UNAUTHORIZED),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("error querying db for user credentials: {e}");
|
||||||
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ use sea_orm::{ColumnTrait, Condition, EntityTrait, Order, QueryFilter, QueryOrde
|
||||||
|
|
||||||
use crate::{server::auth::{AuthIdentity, Identity}, errors::UpubError, model, server::Context, url};
|
use crate::{server::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};
|
||||||
|
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
|
@ -20,7 +20,7 @@ pub async fn page(
|
||||||
let limit = page.batch.unwrap_or(20).min(50);
|
let limit = page.batch.unwrap_or(20).min(50);
|
||||||
let offset = page.offset.unwrap_or(0);
|
let offset = page.offset.unwrap_or(0);
|
||||||
let mut condition = Condition::any()
|
let mut condition = Condition::any()
|
||||||
.add(model::addressing::Column::Actor.eq(PUBLIC_TARGET));
|
.add(model::addressing::Column::Actor.eq(apb::target::PUBLIC));
|
||||||
if let Identity::Local(user) = auth {
|
if let Identity::Local(user) = auth {
|
||||||
condition = condition
|
condition = condition
|
||||||
.add(model::addressing::Column::Actor.eq(user));
|
.add(model::addressing::Column::Actor.eq(user));
|
||||||
|
|
|
@ -3,34 +3,33 @@ pub mod inbox;
|
||||||
pub mod outbox;
|
pub mod outbox;
|
||||||
pub mod object;
|
pub mod object;
|
||||||
pub mod activity;
|
pub mod activity;
|
||||||
|
pub mod application;
|
||||||
|
pub mod auth;
|
||||||
pub mod well_known;
|
pub mod well_known;
|
||||||
pub mod router;
|
|
||||||
|
|
||||||
pub mod jsonld;
|
pub mod jsonld;
|
||||||
pub use jsonld::JsonLD;
|
pub use jsonld::JsonLD;
|
||||||
|
|
||||||
use axum::{extract::State, http::StatusCode, response::IntoResponse, routing::{get, post}, Json, Router};
|
use axum::{http::StatusCode, response::IntoResponse, routing::{get, post}, Router};
|
||||||
use rand::Rng;
|
|
||||||
use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter};
|
|
||||||
|
|
||||||
use apb::{PublicKeyMut, ActorMut, ActorType, Link, Object, ObjectMut, BaseMut, Node};
|
pub trait ActivityPubRouter {
|
||||||
use crate::{model, server::Context, url};
|
fn ap_routes(self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
use self::jsonld::LD;
|
impl ActivityPubRouter for Router<crate::server::Context> {
|
||||||
|
fn ap_routes(self) -> Self {
|
||||||
pub fn router() -> Router<crate::server::Context> {
|
|
||||||
use crate::routes::activitypub as ap; // TODO use self ?
|
use crate::routes::activitypub as ap; // TODO use self ?
|
||||||
|
|
||||||
Router::new()
|
self
|
||||||
// core server inbox/outbox, maybe for feeds? TODO do we need these?
|
// core server inbox/outbox, maybe for feeds? TODO do we need these?
|
||||||
.route("/", get(ap::view))
|
.route("/", get(ap::application::view))
|
||||||
// TODO shared inboxes and instance stream will come later, just use users *boxes for now
|
// TODO shared inboxes and instance stream will come later, just use users *boxes for now
|
||||||
.route("/inbox", get(ap::inbox::get))
|
.route("/inbox", get(ap::inbox::get))
|
||||||
// .route("/inbox", post(ap::inbox::post))
|
// .route("/inbox", post(ap::inbox::post))
|
||||||
// .route("/outbox", get(ap::outbox::get))
|
// .route("/outbox", get(ap::outbox::get))
|
||||||
// .route("/outbox", get(ap::outbox::post))
|
// .route("/outbox", get(ap::outbox::post))
|
||||||
// AUTH routes
|
// AUTH routes
|
||||||
.route("/auth", post(ap::auth))
|
.route("/auth", post(ap::auth::login))
|
||||||
// .well-known and discovery
|
// .well-known and discovery
|
||||||
.route("/.well-known/webfinger", get(ap::well_known::webfinger))
|
.route("/.well-known/webfinger", get(ap::well_known::webfinger))
|
||||||
.route("/.well-known/host-meta", get(ap::well_known::host_meta))
|
.route("/.well-known/host-meta", get(ap::well_known::host_meta))
|
||||||
|
@ -52,41 +51,8 @@ pub fn router() -> Router<crate::server::Context> {
|
||||||
.route("/activities/:id", get(ap::activity::view))
|
.route("/activities/:id", get(ap::activity::view))
|
||||||
.route("/objects/:id", get(ap::object::view))
|
.route("/objects/:id", get(ap::object::view))
|
||||||
}
|
}
|
||||||
|
|
||||||
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 fn split_id(id: &str) -> (String, String) {
|
|
||||||
let clean = id
|
|
||||||
.replace("http://", "")
|
|
||||||
.replace("https://", "");
|
|
||||||
let mut splits = clean.split('/');
|
|
||||||
let first = splits.next().unwrap_or("");
|
|
||||||
let last = splits.last().unwrap_or(first);
|
|
||||||
(first.to_string(), last.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn domain(domain: &str) -> String {
|
|
||||||
domain
|
|
||||||
.replace("http://", "")
|
|
||||||
.replace("https://", "")
|
|
||||||
.replace('/', "")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, serde::Deserialize)]
|
||||||
// TODO i don't really like how pleroma/mastodon do it actually, maybe change this?
|
// TODO i don't really like how pleroma/mastodon do it actually, maybe change this?
|
||||||
pub struct Pagination {
|
pub struct Pagination {
|
||||||
|
@ -104,87 +70,3 @@ impl IntoResponse for CreationResult {
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn view(State(ctx): State<Context>) -> Result<Json<serde_json::Value>, StatusCode> {
|
|
||||||
Ok(Json(
|
|
||||||
serde_json::Value::new_object()
|
|
||||||
.set_id(Some(&url!(ctx, "")))
|
|
||||||
.set_actor_type(Some(ActorType::Application))
|
|
||||||
.set_name(Some("μpub"))
|
|
||||||
.set_summary(Some("micro social network, federated"))
|
|
||||||
.set_published(Some(ctx.app().created))
|
|
||||||
.set_public_key(Node::object(
|
|
||||||
serde_json::Value::new_object()
|
|
||||||
.set_id(Some(&url!(ctx, "#main-key")))
|
|
||||||
.set_owner(Some(&url!(ctx, "")))
|
|
||||||
.set_public_key_pem(&ctx.app().public_key)
|
|
||||||
))
|
|
||||||
.ld_context()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Deserialize)]
|
|
||||||
pub struct LoginForm {
|
|
||||||
email: String,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn auth(State(ctx): State<Context>, Json(login): Json<LoginForm>) -> Result<Json<serde_json::Value>, StatusCode> {
|
|
||||||
// TODO salt the pwd
|
|
||||||
match model::credential::Entity::find()
|
|
||||||
.filter(Condition::all()
|
|
||||||
.add(model::credential::Column::Email.eq(login.email))
|
|
||||||
.add(model::credential::Column::Password.eq(sha256::digest(login.password)))
|
|
||||||
)
|
|
||||||
.one(ctx.db())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(Some(x)) => {
|
|
||||||
// TODO should probably use crypto-safe rng
|
|
||||||
let token : String = rand::thread_rng()
|
|
||||||
.sample_iter(&rand::distributions::Alphanumeric)
|
|
||||||
.take(128)
|
|
||||||
.map(char::from)
|
|
||||||
.collect();
|
|
||||||
model::session::Entity::insert(
|
|
||||||
model::session::ActiveModel {
|
|
||||||
id: sea_orm::ActiveValue::Set(token.clone()),
|
|
||||||
actor: sea_orm::ActiveValue::Set(x.id),
|
|
||||||
expires: sea_orm::ActiveValue::Set(chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6)),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.exec(ctx.db())
|
|
||||||
.await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
Ok(Json(serde_json::Value::String(token)))
|
|
||||||
},
|
|
||||||
Ok(None) => Err(StatusCode::UNAUTHORIZED),
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("error querying db for user credentials: {e}");
|
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::async_trait]
|
|
||||||
pub trait APOutbox {
|
|
||||||
async fn create_note(&self, uid: String, object: serde_json::Value) -> crate::Result<String>;
|
|
||||||
async fn create(&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>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::async_trait]
|
|
||||||
pub trait APInbox {
|
|
||||||
async fn create(&self, activity: serde_json::Value) -> crate::Result<()>;
|
|
||||||
async fn like(&self, activity: serde_json::Value) -> crate::Result<()>;
|
|
||||||
async fn follow(&self, activity: serde_json::Value) -> crate::Result<()>;
|
|
||||||
async fn accept(&self, activity: serde_json::Value) -> crate::Result<()>;
|
|
||||||
async fn reject(&self, activity: serde_json::Value) -> crate::Result<()>;
|
|
||||||
async fn undo(&self, activity: serde_json::Value) -> crate::Result<()>;
|
|
||||||
async fn delete(&self, activity: serde_json::Value) -> crate::Result<()>;
|
|
||||||
async fn update(&self, activity: serde_json::Value) -> crate::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
use axum::{routing::{get, post}, Router};
|
|
||||||
use sea_orm::DatabaseConnection;
|
|
||||||
use crate::routes::activitypub as ap;
|
|
||||||
|
|
||||||
pub async fn serve(db: DatabaseConnection, domain: String) -> std::io::Result<()> {
|
|
||||||
// build our application with a single route
|
|
||||||
let app = Router::new()
|
|
||||||
// core server inbox/outbox, maybe for feeds? TODO do we need these?
|
|
||||||
.route("/", get(ap::view))
|
|
||||||
// TODO shared inboxes and instance stream will come later, just use users *boxes for now
|
|
||||||
.route("/inbox", get(ap::inbox::get))
|
|
||||||
// .route("/inbox", post(ap::inbox::post))
|
|
||||||
// .route("/outbox", get(ap::outbox::get))
|
|
||||||
// .route("/outbox", get(ap::outbox::post))
|
|
||||||
// AUTH routes
|
|
||||||
.route("/auth", post(ap::auth))
|
|
||||||
// .well-known and discovery
|
|
||||||
.route("/.well-known/webfinger", get(ap::well_known::webfinger))
|
|
||||||
.route("/.well-known/host-meta", get(ap::well_known::host_meta))
|
|
||||||
.route("/.well-known/nodeinfo", get(ap::well_known::nodeinfo_discovery))
|
|
||||||
.route("/nodeinfo/:version", get(ap::well_known::nodeinfo))
|
|
||||||
// actor routes
|
|
||||||
.route("/users/:id", get(ap::user::view))
|
|
||||||
.route("/users/:id/inbox", post(ap::user::inbox::post))
|
|
||||||
.route("/users/:id/inbox", get(ap::user::inbox::get))
|
|
||||||
.route("/users/:id/inbox/page", get(ap::user::inbox::page))
|
|
||||||
.route("/users/:id/outbox", post(ap::user::outbox::post))
|
|
||||||
.route("/users/:id/outbox", get(ap::user::outbox::get))
|
|
||||||
.route("/users/:id/outbox/page", get(ap::user::outbox::page))
|
|
||||||
.route("/users/:id/followers", get(ap::user::following::get::<false>))
|
|
||||||
.route("/users/:id/followers/page", get(ap::user::following::page::<false>))
|
|
||||||
.route("/users/:id/following", get(ap::user::following::get::<true>))
|
|
||||||
.route("/users/:id/following/page", get(ap::user::following::page::<true>))
|
|
||||||
// specific object routes
|
|
||||||
.route("/activities/:id", get(ap::activity::view))
|
|
||||||
.route("/objects/:id", get(ap::object::view))
|
|
||||||
.with_state(
|
|
||||||
crate::server::Context::new(db, domain).await.expect("could not create server state")
|
|
||||||
);
|
|
||||||
|
|
||||||
// run our app with hyper, listening locally on port 3000
|
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
|
|
||||||
|
|
||||||
axum::serve(listener, app)
|
|
||||||
.await
|
|
||||||
}
|
|
|
@ -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::{ColumnTrait, Condition, EntityTrait, Order, QueryFilter, QueryOrder, QuerySelect};
|
use sea_orm::{ColumnTrait, Condition, EntityTrait, Order, QueryFilter, QueryOrder, QuerySelect};
|
||||||
|
|
||||||
use apb::{ActivityType, ObjectType, Base, BaseType};
|
use apb::{server::Inbox, ActivityType, Base, BaseType, ObjectType};
|
||||||
use crate::{routes::activitypub::{activity::ap_activity, jsonld::LD, APInbox, JsonLD, Pagination}, server::{Context, auth::{AuthIdentity, Identity}}, errors::UpubError, model, url};
|
use crate::{routes::activitypub::{activity::ap_activity, jsonld::LD, JsonLD, Pagination}, server::{Context, auth::{AuthIdentity, Identity}}, errors::UpubError, model, url};
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
|
|
|
@ -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, Order, QueryOrder, QuerySelect};
|
use sea_orm::{EntityTrait, Order, QueryOrder, QuerySelect};
|
||||||
|
|
||||||
use apb::{AcceptType, ActivityMut, ActivityType, Base, BaseType, Node, ObjectType, RejectType};
|
use apb::{server::Outbox, AcceptType, ActivityMut, ActivityType, Base, BaseType, Node, ObjectType, RejectType};
|
||||||
use crate::{routes::activitypub::{jsonld::LD, APOutbox, CreationResult, JsonLD, Pagination}, server::auth::{AuthIdentity, Identity}, errors::UpubError, model, server::Context, url};
|
use crate::{routes::activitypub::{jsonld::LD, CreationResult, JsonLD, Pagination}, server::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>,
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
use axum::{http::StatusCode, routing::{delete, get, patch, post}, Router};
|
use axum::{http::StatusCode, routing::{delete, get, patch, post}, Router};
|
||||||
use crate::server::Context;
|
use crate::server::Context;
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
async fn todo() -> StatusCode { StatusCode::NOT_IMPLEMENTED }
|
async fn todo() -> StatusCode { StatusCode::NOT_IMPLEMENTED }
|
||||||
|
|
||||||
#[allow(unused)]
|
pub trait MastodonRouter {
|
||||||
pub async fn mastodon_api_routes(router: Router<Context>) -> Router<Context> {
|
fn mastodon_routes(self) -> Self;
|
||||||
router.nest(
|
}
|
||||||
|
|
||||||
|
impl MastodonRouter for Router<Context> {
|
||||||
|
fn mastodon_routes(self) -> Self {
|
||||||
|
self.nest(
|
||||||
// TODO Oauth is just under /oauth
|
// TODO Oauth is just under /oauth
|
||||||
"/api/v1", Router::new()
|
"/api/v1", Router::new()
|
||||||
.route("/apps", post(todo)) // create an application
|
.route("/apps", post(todo)) // create an application
|
||||||
|
@ -64,6 +67,6 @@ pub async fn mastodon_api_routes(router: Router<Context>) -> Router<Context> {
|
||||||
.route("/profile/avatar", delete(todo))
|
.route("/profile/avatar", delete(todo))
|
||||||
.route("/profile/header", delete(todo))
|
.route("/profile/header", delete(todo))
|
||||||
.route("/statuses", post(todo))
|
.route("/statuses", post(todo))
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,3 +5,12 @@ pub mod web;
|
||||||
|
|
||||||
#[cfg(feature = "mastodon")]
|
#[cfg(feature = "mastodon")]
|
||||||
pub mod mastodon;
|
pub mod mastodon;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "mastodon"))]
|
||||||
|
pub mod mastodon {
|
||||||
|
pub trait MastodonRouter {
|
||||||
|
fn mastodon_routes(self) -> Self { self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MastodonRouter for axum::Router<crate::server::Context> {}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use apb::{BaseMut, CollectionMut, CollectionPageMut};
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
||||||
|
|
||||||
use crate::{model, routes::activitypub::{jsonld::LD, PUBLIC_TARGET}};
|
use crate::{model, routes::activitypub::jsonld::LD};
|
||||||
|
|
||||||
use super::{dispatcher::Dispatcher, fetcher::Fetcher};
|
use super::{dispatcher::Dispatcher, fetcher::Fetcher};
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ impl Context {
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|to| !to.is_empty())
|
.filter(|to| !to.is_empty())
|
||||||
.filter(|to| Context::server(to) != self.base())
|
.filter(|to| Context::server(to) != self.base())
|
||||||
.filter(|to| to != &PUBLIC_TARGET)
|
.filter(|to| to != &apb::target::PUBLIC)
|
||||||
.map(|to| model::delivery::ActiveModel {
|
.map(|to| model::delivery::ActiveModel {
|
||||||
id: sea_orm::ActiveValue::NotSet,
|
id: sea_orm::ActiveValue::NotSet,
|
||||||
actor: Set(from.to_string()),
|
actor: Set(from.to_string()),
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
use apb::{Activity, Base, Object};
|
use apb::{target::Addressed, Activity, Base, Object};
|
||||||
use sea_orm::{sea_query::Expr, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set};
|
use sea_orm::{sea_query::Expr, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set};
|
||||||
|
|
||||||
use crate::{errors::{LoggableError, UpubError}, model, routes::activitypub::{APInbox, Addressed}};
|
use crate::{errors::{LoggableError, UpubError}, model};
|
||||||
|
|
||||||
use super::Context;
|
use super::Context;
|
||||||
|
|
||||||
|
|
||||||
#[axum::async_trait]
|
#[axum::async_trait]
|
||||||
impl APInbox for Context {
|
impl apb::server::Inbox for Context {
|
||||||
|
type Error = UpubError;
|
||||||
|
type Activity = serde_json::Value;
|
||||||
|
|
||||||
async fn create(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn create(&self, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
let activity_model = model::activity::Model::new(&activity)?;
|
let activity_model = model::activity::Model::new(&activity)?;
|
||||||
let activity_targets = activity.addressed();
|
let activity_targets = activity.addressed();
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
use apb::{Activity, ActivityMut, BaseMut, Node, ObjectMut};
|
use apb::{target::Addressed, Activity, ActivityMut, BaseMut, Node, ObjectMut};
|
||||||
use sea_orm::{EntityTrait, IntoActiveModel, Set};
|
use sea_orm::{EntityTrait, IntoActiveModel, Set};
|
||||||
|
|
||||||
use crate::{errors::UpubError, model, routes::activitypub::{APOutbox, Addressed}};
|
use crate::{errors::UpubError, model};
|
||||||
|
|
||||||
use super::Context;
|
use super::Context;
|
||||||
|
|
||||||
|
|
||||||
#[axum::async_trait]
|
#[axum::async_trait]
|
||||||
impl APOutbox for Context {
|
impl apb::server::Outbox for Context {
|
||||||
|
type Error = UpubError;
|
||||||
|
type Object = serde_json::Value;
|
||||||
|
type Activity = serde_json::Value;
|
||||||
|
|
||||||
async fn create_note(&self, uid: String, object: serde_json::Value) -> crate::Result<String> {
|
async fn create_note(&self, uid: String, object: serde_json::Value) -> crate::Result<String> {
|
||||||
let oid = self.oid(uuid::Uuid::new_v4().to_string());
|
let oid = self.oid(uuid::Uuid::new_v4().to_string());
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
||||||
|
|
Loading…
Reference in a new issue