1
0
Fork 0
forked from alemi/upub
upub/src/main.rs

214 lines
5.9 KiB
Rust
Raw Normal View History

pub mod server;
pub mod model;
pub mod routes;
2024-04-06 18:03:13 +02:00
pub mod errors;
2024-04-06 18:03:13 +02:00
#[cfg(feature = "migrations")]
mod migrations;
#[cfg(feature = "migrations")]
use sea_orm_migration::MigratorTrait;
use clap::{Parser, Subcommand};
use sea_orm::{ColumnTrait, ConnectOptions, Database, EntityTrait, IntoActiveModel, QueryFilter, QueryOrder};
pub use errors::UpubResult as Result;
2024-04-13 22:31:46 +02:00
use tower_http::{cors::CorsLayer, trace::TraceLayer};
2024-04-13 22:13:36 +02:00
use crate::server::fetcher::Fetchable;
2024-03-25 05:02:20 +01:00
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Parser)]
/// all names were taken
struct CliArgs {
#[clap(subcommand)]
/// command to run
command: CliCommand,
2024-03-20 09:04:08 +01:00
#[arg(short = 'd', long = "db", default_value = "sqlite://./upub.db")]
/// database connection uri
database: String,
2024-03-20 09:04:08 +01:00
#[arg(short = 'D', long, default_value = "http://localhost:3000")]
2024-03-20 08:56:35 +01:00
/// instance base domain, for AP ids
domain: String,
#[arg(long, default_value_t=false)]
/// run with debug level tracing
debug: bool,
}
#[derive(Clone, Subcommand)]
enum CliCommand {
/// run fediverse server
Serve ,
2024-04-06 18:03:13 +02:00
#[cfg(feature = "migrations")]
/// apply database migrations
Migrate,
2024-03-16 05:46:14 +01:00
2024-04-06 18:03:13 +02:00
#[cfg(feature = "faker")]
2024-03-16 05:46:14 +01:00
/// generate fake user, note and activity
Faker{
/// how many fake statuses to insert for root user
2024-03-26 00:58:01 +01:00
count: u64,
},
/// fetch a single AP object
Fetch {
/// object id, or uri, to fetch
uri: String,
#[arg(long, default_value_t = false)]
/// store fetched object in local db
save: bool,
},
2024-04-22 03:56:07 +02:00
/// follow a remote relay
Relay {
/// actor url, same as with pleroma
actor: String,
#[arg(long, default_value_t = false)]
accept: bool
2024-04-22 03:56:07 +02:00
}
}
2024-02-09 17:07:55 +01:00
#[tokio::main]
async fn main() {
let args = CliArgs::parse();
tracing_subscriber::fmt()
.compact()
.with_max_level(if args.debug { tracing::Level::DEBUG } else { tracing::Level::INFO })
.init();
// TODO can i do connectoptions.into() or .connect() and skip these ugly bindings?
let mut opts = ConnectOptions::new(&args.database);
opts
.sqlx_logging_level(tracing::log::LevelFilter::Debug);
let db = Database::connect(opts)
.await.expect("error connecting to db");
match args.command {
2024-04-06 18:03:13 +02:00
#[cfg(feature = "migrations")]
CliCommand::Migrate => migrations::Migrator::up(&db, None)
.await.expect("error applying migrations"),
2024-03-16 05:46:14 +01:00
2024-04-06 18:03:13 +02:00
#[cfg(feature = "faker")]
2024-03-26 00:58:01 +01:00
CliCommand::Faker { count } => model::faker::faker(&db, args.domain, count)
2024-03-16 05:46:14 +01:00
.await.expect("error creating fake entities"),
2024-04-13 22:13:36 +02:00
CliCommand::Fetch { uri, save } => fetch(db, args.domain, uri, save)
.await.expect("error fetching object"),
2024-04-09 04:35:57 +02:00
CliCommand::Relay { actor, accept } => {
2024-04-22 03:56:07 +02:00
let ctx = server::Context::new(db, args.domain)
.await.expect("failed creating server context");
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
let mut activity_model = model::activity::Model {
2024-04-22 03:56:07 +02:00
id: aid.clone(),
activity_type: apb::ActivityType::Follow,
actor: ctx.base(),
object: Some(actor.clone()),
2024-04-22 03:56:07 +02:00
target: None,
published: chrono::Utc::now(),
to: model::Audience(vec![actor.clone()]),
2024-04-22 03:56:07 +02:00
bto: model::Audience::default(),
cc: model::Audience(vec![apb::target::PUBLIC.to_string()]),
bcc: model::Audience::default(),
2024-04-22 03:56:07 +02:00
};
if accept {
let follow_req = model::activity::Entity::find()
.filter(model::activity::Column::ActivityType.eq("Follow"))
.filter(model::activity::Column::Actor.eq(&actor))
.filter(model::activity::Column::Object.eq(ctx.base()))
.order_by_desc(model::activity::Column::Published)
.one(ctx.db())
.await
.expect("failed querying db for relay follow req")
.expect("no follow request to accept");
activity_model.activity_type = apb::ActivityType::Accept(apb::AcceptType::Accept);
activity_model.object = Some(follow_req.id);
};
2024-04-22 03:56:07 +02:00
model::activity::Entity::insert(activity_model.into_active_model())
.exec(ctx.db()).await.expect("could not insert activity in db");
2024-04-23 16:54:03 +02:00
ctx.dispatch(&ctx.base(), vec![actor, apb::target::PUBLIC.to_string()], &aid, None).await
.expect("could not dispatch relay activity");
2024-04-22 03:56:07 +02:00
},
2024-04-09 04:35:57 +02:00
CliCommand::Serve => {
let ctx = server::Context::new(db, args.domain)
.await.expect("failed creating server context");
use routes::activitypub::ActivityPubRouter;
use routes::mastodon::MastodonRouter;
let router = axum::Router::new()
.ap_routes()
.mastodon_routes() // no-op if mastodon feature is disabled
2024-04-13 22:31:46 +02:00
.layer(CorsLayer::permissive())
.layer(TraceLayer::new_for_http())
.with_state(ctx);
2024-04-09 04:35:57 +02:00
// run our app with hyper, listening locally on port 3000
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await.expect("could not bind tcp socket");
axum::serve(listener, router)
.await
.expect("failed serving application")
},
}
}
2024-04-13 22:13:36 +02:00
async fn fetch(db: sea_orm::DatabaseConnection, domain: String, uri: String, save: bool) -> crate::Result<()> {
use apb::Base;
2024-04-13 22:13:36 +02:00
let ctx = server::Context::new(db, domain)
.await.expect("failed creating server context");
let mut node = apb::Node::link(uri.to_string());
node.fetch(&ctx).await?;
let obj = node.get().expect("node still empty after fetch?");
if save {
match obj.base_type() {
Some(apb::BaseType::Object(apb::ObjectType::Actor(_))) => {
model::user::Entity::insert(
model::user::Model::new(obj).unwrap().into_active_model()
2024-04-13 22:13:36 +02:00
).exec(ctx.db()).await.unwrap();
},
Some(apb::BaseType::Object(apb::ObjectType::Activity(_))) => {
model::activity::Entity::insert(
model::activity::Model::new(obj).unwrap().into_active_model()
2024-04-13 22:13:36 +02:00
).exec(ctx.db()).await.unwrap();
},
Some(apb::BaseType::Object(apb::ObjectType::Note)) => {
model::object::Entity::insert(
model::object::Model::new(obj).unwrap().into_active_model()
2024-04-13 22:13:36 +02:00
).exec(ctx.db()).await.unwrap();
},
Some(apb::BaseType::Object(t)) => tracing::warn!("not implemented: {:?}", t),
Some(apb::BaseType::Link(_)) => tracing::error!("fetched another link?"),
None => tracing::error!("no type on object"),
}
}
println!("{}", serde_json::to_string_pretty(&obj).unwrap());
Ok(())
}