forked from alemi/upub
feat: allow specifying base domain
This commit is contained in:
parent
d60c29d4cb
commit
5386c7ba7a
7 changed files with 81 additions and 51 deletions
|
@ -1,14 +1,12 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use axum::{extract::{Path, State}, http::StatusCode, Json};
|
||||
use sea_orm::{DatabaseConnection, EntityTrait};
|
||||
use sea_orm::EntityTrait;
|
||||
|
||||
use crate::{activitystream::Base, model::activity};
|
||||
use crate::{activitystream::Base, model::activity, server::Context};
|
||||
|
||||
|
||||
pub async fn view(State(db) : State<Arc<DatabaseConnection>>, Path(id): Path<String>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
let uri = format!("http://localhost:3000/activities/{id}");
|
||||
match activity::Entity::find_by_id(uri).one(db.deref()).await {
|
||||
match activity::Entity::find_by_id(uri).one(ctx.db()).await {
|
||||
Ok(Some(activity)) => Ok(Json(activity.underlying_json_object())),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
|
|
|
@ -2,22 +2,13 @@ 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 sea_orm::{EntityTrait, IntoActiveModel};
|
||||
|
||||
use crate::{activitystream::{object::{ObjectType, activity::{Activity, ActivityType}}, Base, BaseType, Node}, model};
|
||||
use crate::{activitystream::{object::{activity::{Activity, ActivityType}, ObjectType}, Base, BaseType, Node}, model, server::Context};
|
||||
|
||||
|
||||
pub fn uri_id(entity: &str, id: String) -> String {
|
||||
if id.starts_with("http") { id } else { format!("http://localhost:3000/{entity}/{id}") }
|
||||
}
|
||||
|
||||
pub fn id_uri(id: &str) -> &str {
|
||||
id.split('/').last().unwrap_or("")
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
// TODO i don't really like how pleroma/mastodon do it actually, maybe change this?
|
||||
pub struct Page {
|
||||
|
@ -25,7 +16,7 @@ pub struct Page {
|
|||
pub max_id: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn inbox(State(db) : State<Arc<DatabaseConnection>>, Json(object): Json<serde_json::Value>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
pub async fn inbox(State(ctx) : State<Context>, Json(object): Json<serde_json::Value>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
match object.base_type() {
|
||||
None => { Err(StatusCode::BAD_REQUEST) },
|
||||
Some(BaseType::Link(_x)) => Err(StatusCode::UNPROCESSABLE_ENTITY), // we could but not yet
|
||||
|
@ -44,10 +35,10 @@ pub async fn inbox(State(db) : State<Arc<DatabaseConnection>>, Json(object): Jso
|
|||
return Err(StatusCode::UNPROCESSABLE_ENTITY);
|
||||
};
|
||||
model::object::Entity::insert(obj_entity.into_active_model())
|
||||
.exec(db.deref())
|
||||
.exec(ctx.db())
|
||||
.await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
model::activity::Entity::insert(activity_entity.into_active_model())
|
||||
.exec(db.deref())
|
||||
.exec(ctx.db())
|
||||
.await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
Ok(Json(serde_json::Value::Null)) // TODO hmmmmmmmmmmm not the best value to return....
|
||||
},
|
||||
|
@ -56,6 +47,6 @@ pub async fn inbox(State(db) : State<Arc<DatabaseConnection>>, Json(object): Jso
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn outbox(State(_db): State<Arc<DatabaseConnection>>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
pub async fn outbox(State(_db): State<Context>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
todo!()
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use axum::{extract::{Path, State}, http::StatusCode, Json};
|
||||
use sea_orm::{DatabaseConnection, EntityTrait};
|
||||
use sea_orm::EntityTrait;
|
||||
|
||||
use crate::{activitystream::Base, model::object};
|
||||
use crate::{activitystream::Base, model::object, server::Context};
|
||||
|
||||
|
||||
pub async fn view(State(db) : State<Arc<DatabaseConnection>>, Path(id): Path<String>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
let uri = format!("http://localhost:3000/objects/{id}");
|
||||
match object::Entity::find_by_id(uri).one(db.deref()).await {
|
||||
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
match object::Entity::find_by_id(ctx.uri("objects", id)).one(ctx.db()).await {
|
||||
Ok(Some(object)) => Ok(Json(object.underlying_json_object())),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
|
|
|
@ -3,14 +3,14 @@ use std::sync::Arc;
|
|||
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
||||
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, IntoActiveModel, Order, QueryFilter, QueryOrder, QuerySelect};
|
||||
|
||||
use crate::{activitystream::{self, object::{activity::{Activity, ActivityType}, collection::{page::CollectionPageMut, CollectionMut, CollectionType}, ObjectType}, Base, BaseMut, BaseType, Node}, model::{self, activity, object, user}};
|
||||
use crate::{activitystream::{self, object::{activity::{Activity, ActivityType}, collection::{page::CollectionPageMut, CollectionMut, CollectionType}, ObjectType}, Base, BaseMut, BaseType, Node}, model::{self, activity, object, user}, server::Context};
|
||||
|
||||
pub async fn list(State(_db) : State<Arc<DatabaseConnection>>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn view(State(db) : State<Arc<DatabaseConnection>>, Path(id): Path<String>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
match user::Entity::find_by_id(super::uri_id("users", id)).one(&*db).await {
|
||||
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
match user::Entity::find_by_id(ctx.uri("users", id)).one(ctx.db()).await {
|
||||
Ok(Some(user)) => Ok(Json(user.underlying_json_object())),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
|
@ -21,7 +21,7 @@ pub async fn view(State(db) : State<Arc<DatabaseConnection>>, Path(id): Path<Str
|
|||
}
|
||||
|
||||
pub async fn outbox(
|
||||
State(db): State<Arc<DatabaseConnection>>,
|
||||
State(ctx): State<Context>,
|
||||
Path(id): Path<String>,
|
||||
Query(page): Query<super::Page>,
|
||||
) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
|
@ -29,8 +29,8 @@ pub async fn outbox(
|
|||
|
||||
// find requested recent post, to filter based on its date (use now() as fallback)
|
||||
let before = if let Some(before) = page.max_id {
|
||||
match model::activity::Entity::find_by_id(super::uri_id("activities", before))
|
||||
.one(&*db).await
|
||||
match model::activity::Entity::find_by_id(ctx.uri("activities", before))
|
||||
.one(ctx.db()).await
|
||||
{
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Ok(Some(x)) => x.published,
|
||||
|
@ -45,11 +45,11 @@ pub async fn outbox(
|
|||
.filter(Condition::all().add(activity::Column::Published.lt(before)))
|
||||
.order_by(activity::Column::Published, Order::Desc)
|
||||
.limit(20) // TODO allow customizing, with boundaries
|
||||
.all(&*db).await
|
||||
.all(ctx.db()).await
|
||||
{
|
||||
Err(_e) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
Ok(items) => {
|
||||
let next = super::id_uri(&items.last().unwrap().id).to_string();
|
||||
let next = ctx.id(items.last().map(|x| x.id.as_str()).unwrap_or("").to_string());
|
||||
let items = items
|
||||
.into_iter()
|
||||
.map(|i| i.underlying_json_object())
|
||||
|
@ -76,7 +76,7 @@ pub async fn outbox(
|
|||
}
|
||||
|
||||
pub async fn inbox(
|
||||
State(db): State<Arc<DatabaseConnection>>,
|
||||
State(ctx): State<Context>,
|
||||
Path(_id): Path<String>,
|
||||
Json(object): Json<serde_json::Value>
|
||||
) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
|
@ -98,10 +98,10 @@ pub async fn inbox(
|
|||
return Err(StatusCode::UNPROCESSABLE_ENTITY);
|
||||
};
|
||||
object::Entity::insert(obj_entity.into_active_model())
|
||||
.exec(&*db)
|
||||
.exec(ctx.db())
|
||||
.await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
activity::Entity::insert(activity_entity.into_active_model())
|
||||
.exec(&*db)
|
||||
.exec(ctx.db())
|
||||
.await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
Ok(Json(serde_json::Value::Null)) // TODO hmmmmmmmmmmm not the best value to return....
|
||||
},
|
||||
|
|
|
@ -21,6 +21,10 @@ struct CliArgs {
|
|||
/// database connection uri
|
||||
database: String,
|
||||
|
||||
#[arg(short, long, default_value = "http://localhost:3000")]
|
||||
/// instance base domain, for AP ids
|
||||
domain: String,
|
||||
|
||||
#[arg(long, default_value_t=false)]
|
||||
/// run with debug level tracing
|
||||
debug: bool,
|
||||
|
@ -66,13 +70,13 @@ async fn main() {
|
|||
.await.expect("error connecting to db");
|
||||
|
||||
match args.command {
|
||||
CliCommand::Serve => server::serve(db)
|
||||
CliCommand::Serve => server::serve(db, args.domain)
|
||||
.await,
|
||||
|
||||
CliCommand::Migrate => migrations::Migrator::up(&db, None)
|
||||
.await.expect("error applying migrations"),
|
||||
|
||||
CliCommand::Faker => model::faker(&db)
|
||||
CliCommand::Faker => model::faker(&db, args.domain)
|
||||
.await.expect("error creating fake entities"),
|
||||
|
||||
CliCommand::Fetch { uri, save } => fetch(&db, &uri, save)
|
||||
|
|
|
@ -6,11 +6,11 @@ pub mod activity;
|
|||
#[error("missing required field: '{0}'")]
|
||||
pub struct FieldError(pub &'static str);
|
||||
|
||||
pub async fn faker(db: &sea_orm::DatabaseConnection) -> Result<(), sea_orm::DbErr> {
|
||||
pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String) -> Result<(), sea_orm::DbErr> {
|
||||
use sea_orm::EntityTrait;
|
||||
|
||||
user::Entity::insert(user::ActiveModel {
|
||||
id: sea_orm::Set("http://localhost:3000/users/root".into()),
|
||||
id: sea_orm::Set(format!("{domain}/users/root")),
|
||||
name: sea_orm::Set("root".into()),
|
||||
actor_type: sea_orm::Set(super::activitystream::object::actor::ActorType::Person),
|
||||
}).exec(db).await?;
|
||||
|
@ -19,20 +19,20 @@ pub async fn faker(db: &sea_orm::DatabaseConnection) -> Result<(), sea_orm::DbEr
|
|||
let oid = uuid::Uuid::new_v4();
|
||||
let aid = uuid::Uuid::new_v4();
|
||||
object::Entity::insert(object::ActiveModel {
|
||||
id: sea_orm::Set(format!("http://localhost:3000/objects/{oid}")),
|
||||
id: sea_orm::Set(format!("{domain}/objects/{oid}")),
|
||||
name: sea_orm::Set(None),
|
||||
object_type: sea_orm::Set(crate::activitystream::object::ObjectType::Note),
|
||||
attributed_to: sea_orm::Set(Some("http://localhost:3000/users/root".into())),
|
||||
attributed_to: sea_orm::Set(Some(format!("{domain}/users/root"))),
|
||||
summary: sea_orm::Set(None),
|
||||
content: sea_orm::Set(Some(format!("Hello world! {i}"))),
|
||||
content: sea_orm::Set(Some(format!("[{i}] Tic(k). Quasiparticle of intensive multiplicity. Tics (or ticks) are intrinsically several components of autonomously numbering anorganic populations, propagating by contagion between segmentary divisions in the order of nature. Ticks - as nonqualitative differentially-decomposable counting marks - each designate a multitude comprehended as a singular variation in tic(k)-density."))),
|
||||
published: sea_orm::Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)),
|
||||
}).exec(db).await?;
|
||||
|
||||
activity::Entity::insert(activity::ActiveModel {
|
||||
id: sea_orm::Set(format!("http://localhost:3000/activities/{aid}")),
|
||||
id: sea_orm::Set(format!("{domain}/activities/{aid}")),
|
||||
activity_type: sea_orm::Set(crate::activitystream::object::activity::ActivityType::Create),
|
||||
actor: sea_orm::Set("http://localhost:3000/users/root".into()),
|
||||
object: sea_orm::Set(Some(format!("http://localhost:3000/objects/{oid}"))),
|
||||
actor: sea_orm::Set(format!("{domain}/users/root")),
|
||||
object: sea_orm::Set(Some(format!("{domain}/objects/{oid}"))),
|
||||
target: sea_orm::Set(None),
|
||||
published: sea_orm::Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)),
|
||||
}).exec(db).await?;
|
||||
|
|
|
@ -4,7 +4,47 @@ use axum::{routing::{get, post}, Router};
|
|||
use sea_orm::DatabaseConnection;
|
||||
use crate::activitypub as ap;
|
||||
|
||||
pub async fn serve(db: DatabaseConnection) {
|
||||
#[derive(Clone)]
|
||||
pub struct Context(Arc<ContextInner>);
|
||||
struct ContextInner {
|
||||
db: DatabaseConnection,
|
||||
domain: String,
|
||||
}
|
||||
impl Context {
|
||||
pub fn new(db: DatabaseConnection, mut domain: String) -> Self {
|
||||
if !domain.starts_with("http") {
|
||||
domain = format!("https://{domain}");
|
||||
}
|
||||
if domain.ends_with('/') {
|
||||
domain.replace_range(domain.len()-1.., "");
|
||||
}
|
||||
Context(Arc::new(ContextInner { db, domain }))
|
||||
}
|
||||
|
||||
pub fn db(&self) -> &DatabaseConnection {
|
||||
&self.0.db
|
||||
}
|
||||
|
||||
pub fn uri(&self, entity: &str, id: String) -> String {
|
||||
if id.starts_with("http") { id } else {
|
||||
format!("{}/{}/{}", self.0.domain, entity, id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self, id: String) -> String {
|
||||
if id.starts_with(&self.0.domain) {
|
||||
let mut out = id.replace(&self.0.domain, "");
|
||||
if out.ends_with('/') {
|
||||
out.replace_range(out.len()-1.., "");
|
||||
}
|
||||
out
|
||||
} else {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn serve(db: DatabaseConnection, domain: String) {
|
||||
// build our application with a single route
|
||||
let app = Router::new()
|
||||
// core server inbox/outbox, maybe for feeds? TODO do we need these?
|
||||
|
@ -17,7 +57,7 @@ pub async fn serve(db: DatabaseConnection) {
|
|||
// specific object routes
|
||||
.route("/activities/:id", get(ap::activity::view))
|
||||
.route("/objects/:id", get(ap::object::view))
|
||||
.with_state(Arc::new(db));
|
||||
.with_state(Context::new(db, domain));
|
||||
|
||||
// run our app with hyper, listening globally on port 3000
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
|
|
Loading…
Reference in a new issue