2021-01-09 04:35:35 +00:00
|
|
|
use crate::{error::Error, State};
|
2021-01-06 08:21:37 +00:00
|
|
|
use activitystreams::base::AnyBase;
|
|
|
|
use actix_web::{web, HttpResponse, Scope};
|
|
|
|
use hyaenidae_profiles::{apub::ApubIds, Spawner};
|
|
|
|
use sled::Tree;
|
|
|
|
use url::Url;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub(super) struct Apub {
|
|
|
|
base_url: Url,
|
|
|
|
uuid_url: Tree,
|
|
|
|
url_uuid: Tree,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn scope() -> Scope {
|
|
|
|
web::scope("/apub")
|
|
|
|
.service(
|
|
|
|
web::scope("/objects/{id}")
|
|
|
|
.route("", web::get().to(serve_object))
|
|
|
|
.route("/inbox", web::post().to(inbox)),
|
|
|
|
)
|
|
|
|
.route("/inbox", web::post().to(shared_inbox))
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Authorized fetch
|
|
|
|
async fn serve_object(
|
|
|
|
path: web::Path<Uuid>,
|
|
|
|
state: web::Data<State>,
|
2021-01-09 04:35:35 +00:00
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
let url = match state.apub.id_for_uuid(path.into_inner())? {
|
2021-01-06 08:21:37 +00:00
|
|
|
Some(url) => url,
|
|
|
|
None => return Ok(crate::to_404()),
|
|
|
|
};
|
|
|
|
|
|
|
|
let object = match state.profiles.apub.serve_object(&url)? {
|
|
|
|
Some(object) => object,
|
|
|
|
None => return Ok(crate::to_404()),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(HttpResponse::Ok()
|
|
|
|
.content_type("application/activity+json")
|
|
|
|
.json(&object))
|
|
|
|
}
|
|
|
|
|
2021-01-09 04:35:35 +00:00
|
|
|
async fn inbox(body: web::Json<AnyBase>, state: web::Data<State>) -> Result<HttpResponse, Error> {
|
|
|
|
do_inbox(body.into_inner(), &state).await
|
2021-01-06 08:21:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn shared_inbox(
|
|
|
|
body: web::Json<AnyBase>,
|
|
|
|
state: web::Data<State>,
|
2021-01-09 04:35:35 +00:00
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
do_inbox(body.into_inner(), &state).await
|
2021-01-06 08:21:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: signature validation, Actor check
|
|
|
|
async fn do_inbox(any_base: AnyBase, state: &State) -> Result<HttpResponse, Error> {
|
|
|
|
state.spawn.process(any_base, vec![]);
|
|
|
|
Ok(HttpResponse::Created().finish())
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Apub {
|
|
|
|
pub(super) fn build(base_url: Url, db: &sled::Db) -> Result<Self, sled::Error> {
|
|
|
|
Ok(Apub {
|
|
|
|
base_url,
|
|
|
|
uuid_url: db.open_tree("/main/apub/uuid_url")?,
|
|
|
|
url_uuid: db.open_tree("/main/apub/url_uuid")?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn id_for_uuid(&self, uuid: Uuid) -> Result<Option<Url>, Error> {
|
|
|
|
Ok(self.uuid_url.get(uuid.as_bytes())?.and_then(url_from_ivec))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ApubIds for Apub {
|
|
|
|
fn gen_id(&self) -> Option<Url> {
|
|
|
|
let mut url = self.base_url.clone();
|
|
|
|
|
|
|
|
let mut uuid;
|
|
|
|
while {
|
|
|
|
uuid = Uuid::new_v4();
|
|
|
|
url.set_path(&format!("/apub/objects/{}", uuid));
|
|
|
|
|
|
|
|
self.uuid_url
|
|
|
|
.compare_and_swap(uuid.as_bytes(), None as Option<&[u8]>, Some(url_key(&url)))
|
|
|
|
.ok()?
|
|
|
|
.is_err()
|
|
|
|
} {}
|
|
|
|
self.url_uuid.insert(url_key(&url), uuid.as_bytes()).ok()?;
|
|
|
|
|
|
|
|
Some(url)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn public_key(&self, id: &Url) -> Option<Url> {
|
|
|
|
let mut url = id.to_owned();
|
|
|
|
url.set_fragment(Some("main-key"));
|
|
|
|
Some(url)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn following(&self, id: &Url) -> Option<Url> {
|
|
|
|
Url::parse(&format!("{}/following", id)).ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn followers(&self, id: &Url) -> Option<Url> {
|
|
|
|
Url::parse(&format!("{}/followers", id)).ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn inbox(&self, id: &Url) -> Option<Url> {
|
|
|
|
Url::parse(&format!("{}/inbox", id)).ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn outbox(&self, id: &Url) -> Option<Url> {
|
|
|
|
Url::parse(&format!("{}/outbox", id)).ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn shared_inbox(&self) -> Url {
|
|
|
|
let mut url = self.base_url.clone();
|
|
|
|
url.set_path("/apub/inbox");
|
|
|
|
url
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn url_from_ivec(ivec: sled::IVec) -> Option<Url> {
|
|
|
|
String::from_utf8_lossy(&ivec).parse().ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn url_key(url: &Url) -> &[u8] {
|
|
|
|
url.as_str().as_bytes()
|
|
|
|
}
|