Compare commits
6 commits
ef97000c23
...
79bc9c938b
Author | SHA1 | Date | |
---|---|---|---|
79bc9c938b | |||
d641924646 | |||
735cef762b | |||
0a0facae79 | |||
fc572f1c69 | |||
15746c699f |
16 changed files with 192 additions and 56 deletions
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["apb", "web"]
|
members = ["apb", "web", "mdhtml"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "upub"
|
name = "upub"
|
||||||
|
@ -24,6 +24,8 @@ chrono = { version = "0.4", features = ["serde"] }
|
||||||
uuid = { version = "1.8", features = ["v4"] }
|
uuid = { version = "1.8", features = ["v4"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
serde_default = "0.1"
|
||||||
|
serde-inline-default = "0.2"
|
||||||
mdhtml = { path = "mdhtml", features = ["markdown"] }
|
mdhtml = { path = "mdhtml", features = ["markdown"] }
|
||||||
jrd = "0.1"
|
jrd = "0.1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
@ -35,7 +37,7 @@ sea-orm = { version = "0.12", features = ["macros", "sqlx-sqlite", "runtime-toki
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
axum = "0.7"
|
axum = "0.7"
|
||||||
tower-http = { version = "0.5", features = ["cors", "trace"] }
|
tower-http = { version = "0.5", features = ["cors", "trace"] }
|
||||||
apb = { path = "apb", features = ["unstructured", "orm", "activitypub-fe", "activitypub-counters"] }
|
apb = { path = "apb", features = ["unstructured", "orm", "activitypub-fe", "activitypub-counters", "litepub"] }
|
||||||
# nodeinfo = "0.0.2" # the version on crates.io doesn't re-export necessary types to build the struct!!!
|
# nodeinfo = "0.0.2" # the version on crates.io doesn't re-export necessary types to build the struct!!!
|
||||||
nodeinfo = { git = "https://codeberg.org/thefederationinfo/nodeinfo-rs", rev = "e865094804" }
|
nodeinfo = { git = "https://codeberg.org/thefederationinfo/nodeinfo-rs", rev = "e865094804" }
|
||||||
# migrations
|
# migrations
|
||||||
|
@ -44,6 +46,7 @@ sea-orm-migration = { version = "0.12", optional = true }
|
||||||
mastodon-async-entities = { version = "1.1.0", optional = true }
|
mastodon-async-entities = { version = "1.1.0", optional = true }
|
||||||
time = { version = "0.3", features = ["serde"], optional = true }
|
time = { version = "0.3", features = ["serde"], optional = true }
|
||||||
async-recursion = "1.1"
|
async-recursion = "1.1"
|
||||||
|
toml = "0.8.12"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["migrations", "cli"]
|
default = ["migrations", "cli"]
|
||||||
|
|
|
@ -29,6 +29,7 @@ default = ["activitypub-miscellaneous-terms"]
|
||||||
activitypub-miscellaneous-terms = [] # https://swicg.github.io/miscellany/
|
activitypub-miscellaneous-terms = [] # https://swicg.github.io/miscellany/
|
||||||
activitypub-counters = [] # https://ns.alemi.dev/as/counters/#
|
activitypub-counters = [] # https://ns.alemi.dev/as/counters/#
|
||||||
activitypub-fe = [] # https://ns.alemi.dev/as/fe/#
|
activitypub-fe = [] # https://ns.alemi.dev/as/fe/#
|
||||||
|
litepub = [] # incomplete, https://litepub.social/
|
||||||
# builtin utils
|
# builtin utils
|
||||||
orm = ["dep:sea-orm"]
|
orm = ["dep:sea-orm"]
|
||||||
fetch = ["dep:reqwest"]
|
fetch = ["dep:reqwest"]
|
||||||
|
|
|
@ -11,6 +11,38 @@ use offer::OfferType;
|
||||||
use intransitive::IntransitiveActivityType;
|
use intransitive::IntransitiveActivityType;
|
||||||
use ignore::IgnoreType;
|
use ignore::IgnoreType;
|
||||||
|
|
||||||
|
#[cfg(feature = "litepub")]
|
||||||
|
crate::strenum! {
|
||||||
|
pub enum ActivityType {
|
||||||
|
Activity,
|
||||||
|
Add,
|
||||||
|
Announce,
|
||||||
|
Create,
|
||||||
|
Delete,
|
||||||
|
Dislike,
|
||||||
|
EmojiReact,
|
||||||
|
Flag,
|
||||||
|
Follow,
|
||||||
|
Join,
|
||||||
|
Leave,
|
||||||
|
Like,
|
||||||
|
Listen,
|
||||||
|
Move,
|
||||||
|
Read,
|
||||||
|
Remove,
|
||||||
|
Undo,
|
||||||
|
Update,
|
||||||
|
View;
|
||||||
|
|
||||||
|
IntransitiveActivity(IntransitiveActivityType),
|
||||||
|
Accept(AcceptType),
|
||||||
|
Ignore(IgnoreType),
|
||||||
|
Offer(OfferType),
|
||||||
|
Reject(RejectType)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "litepub"))]
|
||||||
crate::strenum! {
|
crate::strenum! {
|
||||||
pub enum ActivityType {
|
pub enum ActivityType {
|
||||||
Activity,
|
Activity,
|
||||||
|
|
|
@ -2,14 +2,17 @@ use crate::model::{addressing, config, credential, activity, object, user, Audie
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use sea_orm::IntoActiveModel;
|
use sea_orm::IntoActiveModel;
|
||||||
|
|
||||||
pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String, count: u64) -> Result<(), sea_orm::DbErr> {
|
pub async fn faker(ctx: crate::server::Context, count: u64) -> Result<(), sea_orm::DbErr> {
|
||||||
use sea_orm::{EntityTrait, Set};
|
use sea_orm::{EntityTrait, Set};
|
||||||
|
|
||||||
|
let domain = ctx.domain();
|
||||||
|
let db = ctx.db();
|
||||||
|
|
||||||
let key = Rsa::generate(2048).unwrap();
|
let key = Rsa::generate(2048).unwrap();
|
||||||
let test_user = user::Model {
|
let test_user = user::Model {
|
||||||
id: format!("{domain}/users/test"),
|
id: format!("{domain}/users/test"),
|
||||||
name: Some("μpub".into()),
|
name: Some("μpub".into()),
|
||||||
domain: clean_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,
|
||||||
|
|
|
@ -2,12 +2,9 @@ use sea_orm::{EntityTrait, IntoActiveModel};
|
||||||
|
|
||||||
use crate::server::fetcher::Fetchable;
|
use crate::server::fetcher::Fetchable;
|
||||||
|
|
||||||
pub async fn fetch(db: sea_orm::DatabaseConnection, domain: String, uri: String, save: bool) -> crate::Result<()> {
|
pub async fn fetch(ctx: crate::server::Context, uri: String, save: bool) -> crate::Result<()> {
|
||||||
use apb::Base;
|
use apb::Base;
|
||||||
|
|
||||||
let ctx = crate::server::Context::new(db, domain)
|
|
||||||
.await.expect("failed creating server context");
|
|
||||||
|
|
||||||
let mut node = apb::Node::link(uri.to_string());
|
let mut node = apb::Node::link(uri.to_string());
|
||||||
node.fetch(&ctx).await?;
|
node.fetch(&ctx).await?;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
use sea_orm::EntityTrait;
|
use sea_orm::EntityTrait;
|
||||||
|
|
||||||
|
|
||||||
pub async fn fix(db: sea_orm::DatabaseConnection, likes: bool, shares: bool, replies: bool) -> crate::Result<()> {
|
pub async fn fix(ctx: crate::server::Context, likes: bool, shares: bool, replies: bool) -> crate::Result<()> {
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
|
let db = ctx.db();
|
||||||
|
|
||||||
if likes {
|
if likes {
|
||||||
tracing::info!("fixing likes...");
|
tracing::info!("fixing likes...");
|
||||||
let mut store = std::collections::HashMap::new();
|
let mut store = std::collections::HashMap::new();
|
||||||
{
|
{
|
||||||
let mut stream = crate::model::like::Entity::find().stream(&db).await?;
|
let mut stream = crate::model::like::Entity::find().stream(db).await?;
|
||||||
while let Some(like) = stream.try_next().await? {
|
while let Some(like) = stream.try_next().await? {
|
||||||
store.insert(like.likes.clone(), store.get(&like.likes).unwrap_or(&0) + 1);
|
store.insert(like.likes.clone(), store.get(&like.likes).unwrap_or(&0) + 1);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +22,7 @@ pub async fn fix(db: sea_orm::DatabaseConnection, likes: bool, shares: bool, rep
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
if let Err(e) = crate::model::object::Entity::update(m)
|
if let Err(e) = crate::model::object::Entity::update(m)
|
||||||
.exec(&db)
|
.exec(db)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
tracing::warn!("record not updated ({k}): {e}");
|
tracing::warn!("record not updated ({k}): {e}");
|
||||||
|
@ -33,7 +34,7 @@ pub async fn fix(db: sea_orm::DatabaseConnection, likes: bool, shares: bool, rep
|
||||||
tracing::info!("fixing shares...");
|
tracing::info!("fixing shares...");
|
||||||
let mut store = std::collections::HashMap::new();
|
let mut store = std::collections::HashMap::new();
|
||||||
{
|
{
|
||||||
let mut stream = crate::model::share::Entity::find().stream(&db).await?;
|
let mut stream = crate::model::share::Entity::find().stream(db).await?;
|
||||||
while let Some(share) = stream.try_next().await? {
|
while let Some(share) = stream.try_next().await? {
|
||||||
store.insert(share.shares.clone(), store.get(&share.shares).unwrap_or(&0) + 1);
|
store.insert(share.shares.clone(), store.get(&share.shares).unwrap_or(&0) + 1);
|
||||||
}
|
}
|
||||||
|
@ -46,7 +47,7 @@ pub async fn fix(db: sea_orm::DatabaseConnection, likes: bool, shares: bool, rep
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
if let Err(e) = crate::model::object::Entity::update(m)
|
if let Err(e) = crate::model::object::Entity::update(m)
|
||||||
.exec(&db)
|
.exec(db)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
tracing::warn!("record not updated ({k}): {e}");
|
tracing::warn!("record not updated ({k}): {e}");
|
||||||
|
@ -58,7 +59,7 @@ pub async fn fix(db: sea_orm::DatabaseConnection, likes: bool, shares: bool, rep
|
||||||
tracing::info!("fixing replies...");
|
tracing::info!("fixing replies...");
|
||||||
let mut store = std::collections::HashMap::new();
|
let mut store = std::collections::HashMap::new();
|
||||||
{
|
{
|
||||||
let mut stream = crate::model::object::Entity::find().stream(&db).await?;
|
let mut stream = crate::model::object::Entity::find().stream(db).await?;
|
||||||
while let Some(object) = stream.try_next().await? {
|
while let Some(object) = stream.try_next().await? {
|
||||||
if let Some(reply) = object.in_reply_to {
|
if let Some(reply) = object.in_reply_to {
|
||||||
let before = store.get(&reply).unwrap_or(&0);
|
let before = store.get(&reply).unwrap_or(&0);
|
||||||
|
@ -74,7 +75,7 @@ pub async fn fix(db: sea_orm::DatabaseConnection, likes: bool, shares: bool, rep
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
if let Err(e) = crate::model::object::Entity::update(m)
|
if let Err(e) = crate::model::object::Entity::update(m)
|
||||||
.exec(&db)
|
.exec(db)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
tracing::warn!("record not updated ({k}): {e}");
|
tracing::warn!("record not updated ({k}): {e}");
|
||||||
|
|
|
@ -10,8 +10,8 @@ pub use faker::*;
|
||||||
mod relay;
|
mod relay;
|
||||||
pub use relay::*;
|
pub use relay::*;
|
||||||
|
|
||||||
mod register;
|
//mod register;
|
||||||
pub use register::*;
|
//pub use register::*;
|
||||||
|
|
||||||
mod update;
|
mod update;
|
||||||
pub use update::*;
|
pub use update::*;
|
||||||
|
@ -71,17 +71,21 @@ pub async fn run(
|
||||||
command: CliCommand,
|
command: CliCommand,
|
||||||
db: sea_orm::DatabaseConnection,
|
db: sea_orm::DatabaseConnection,
|
||||||
domain: String,
|
domain: String,
|
||||||
|
config: crate::config::Config,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
|
let ctx = crate::server::Context::new(
|
||||||
|
db, domain, config,
|
||||||
|
).await?;
|
||||||
match command {
|
match command {
|
||||||
CliCommand::Faker { count } =>
|
CliCommand::Faker { count } =>
|
||||||
Ok(faker(&db, domain, count).await?),
|
Ok(faker(ctx, count).await?),
|
||||||
CliCommand::Fetch { uri, save } =>
|
CliCommand::Fetch { uri, save } =>
|
||||||
Ok(fetch(db, domain, uri, save).await?),
|
Ok(fetch(ctx, uri, save).await?),
|
||||||
CliCommand::Relay { actor, accept } =>
|
CliCommand::Relay { actor, accept } =>
|
||||||
Ok(relay(db, domain, actor, accept).await?),
|
Ok(relay(ctx, actor, accept).await?),
|
||||||
CliCommand::Fix { likes, shares, replies } =>
|
CliCommand::Fix { likes, shares, replies } =>
|
||||||
Ok(fix(db, likes, shares, replies).await?),
|
Ok(fix(ctx, likes, shares, replies).await?),
|
||||||
CliCommand::Update { days } =>
|
CliCommand::Update { days } =>
|
||||||
Ok(update_users(db, domain, days).await?),
|
Ok(update_users(ctx, days).await?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, QueryOrder};
|
use sea_orm::{ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, QueryOrder};
|
||||||
|
|
||||||
pub async fn relay(db: sea_orm::DatabaseConnection, domain: String, actor: String, accept: bool) -> crate::Result<()> {
|
pub async fn relay(ctx: crate::server::Context, actor: String, accept: bool) -> crate::Result<()> {
|
||||||
let ctx = crate::server::Context::new(db, domain).await?;
|
|
||||||
|
|
||||||
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
||||||
|
|
||||||
let mut activity_model = crate::model::activity::Model {
|
let mut activity_model = crate::model::activity::Model {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};
|
use sea_orm::{ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};
|
||||||
|
|
||||||
use crate::server::{fetcher::Fetcher, Context};
|
use crate::server::fetcher::Fetcher;
|
||||||
|
|
||||||
pub async fn update_users(db: sea_orm::DatabaseConnection, domain: String, days: i64) -> crate::Result<()> {
|
pub async fn update_users(ctx: crate::server::Context, days: i64) -> crate::Result<()> {
|
||||||
let ctx = Context::new(db, domain).await?;
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut insertions = Vec::new();
|
let mut insertions = Vec::new();
|
||||||
|
|
||||||
|
|
74
src/config.rs
Normal file
74
src/config.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
|
||||||
|
|
||||||
|
#[serde_inline_default::serde_inline_default]
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, serde_default::DefaultFromSerde)]
|
||||||
|
pub struct Config {
|
||||||
|
#[serde(default)]
|
||||||
|
pub instance: InstanceConfig,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub datasource: DatasourceConfig,
|
||||||
|
|
||||||
|
// TODO should i move app keys here?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[serde_inline_default::serde_inline_default]
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, serde_default::DefaultFromSerde)]
|
||||||
|
pub struct DatasourceConfig {
|
||||||
|
#[serde_inline_default("sqlite://./upub.db".into())]
|
||||||
|
pub connection_string: String,
|
||||||
|
|
||||||
|
#[serde_inline_default(4)]
|
||||||
|
pub max_connections: u32,
|
||||||
|
|
||||||
|
#[serde_inline_default(1)]
|
||||||
|
pub min_connections: u32,
|
||||||
|
|
||||||
|
#[serde_inline_default(300u64)]
|
||||||
|
pub connect_timeout_seconds: u64,
|
||||||
|
|
||||||
|
#[serde_inline_default(300u64)]
|
||||||
|
pub acquire_timeout_seconds: u64,
|
||||||
|
|
||||||
|
#[serde_inline_default(1u64)]
|
||||||
|
pub slow_query_warn_seconds: u64,
|
||||||
|
|
||||||
|
#[serde_inline_default(true)]
|
||||||
|
pub slow_query_warn_enable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[serde_inline_default::serde_inline_default]
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, serde_default::DefaultFromSerde)]
|
||||||
|
pub struct InstanceConfig {
|
||||||
|
#[serde_inline_default("μpub".into())]
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
#[serde_inline_default("micro social network, federated".into())]
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
#[serde_inline_default("upub.social".into())]
|
||||||
|
pub domain: String,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub contact: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub frontend: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn load(path: Option<std::path::PathBuf>) -> Self {
|
||||||
|
let Some(cfg_path) = path else { return Config::default() };
|
||||||
|
match std::fs::read_to_string(cfg_path) {
|
||||||
|
Ok(x) => match toml::from_str(&x) {
|
||||||
|
Ok(cfg) => return cfg,
|
||||||
|
Err(e) => tracing::error!("failed parsing config file: {e}"),
|
||||||
|
},
|
||||||
|
Err(e) => tracing::error!("failed reading config file: {e}"),
|
||||||
|
}
|
||||||
|
Config::default()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use axum::{http::StatusCode, response::{IntoResponse, Redirect}};
|
use axum::{http::StatusCode, response::Redirect};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum UpubError {
|
pub enum UpubError {
|
||||||
|
@ -79,12 +79,7 @@ impl axum::response::IntoResponse for UpubError {
|
||||||
UpubError::Redirect(to) => Redirect::to(&to).into_response(),
|
UpubError::Redirect(to) => Redirect::to(&to).into_response(),
|
||||||
UpubError::Status(status) => (status, descr).into_response(),
|
UpubError::Status(status) => (status, descr).into_response(),
|
||||||
UpubError::Database(_) => (StatusCode::SERVICE_UNAVAILABLE, descr).into_response(),
|
UpubError::Database(_) => (StatusCode::SERVICE_UNAVAILABLE, descr).into_response(),
|
||||||
UpubError::FetchError(e, body) =>
|
UpubError::Reqwest(x) | UpubError::FetchError(x, _) =>
|
||||||
(
|
|
||||||
e.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
|
||||||
format!("failed fetching '{}': {descr} -- server responded with {body}", e.url().map(|x| x.to_string()).unwrap_or_default()),
|
|
||||||
).into_response(),
|
|
||||||
UpubError::Reqwest(x) =>
|
|
||||||
(
|
(
|
||||||
x.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
x.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
||||||
format!("failed fetching '{}': {descr}", x.url().map(|x| x.to_string()).unwrap_or_default())
|
format!("failed fetching '{}': {descr}", x.url().map(|x| x.to_string()).unwrap_or_default())
|
||||||
|
|
45
src/main.rs
45
src/main.rs
|
@ -3,7 +3,6 @@ mod model;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
mod errors;
|
mod errors;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
|
@ -14,6 +13,8 @@ mod migrations;
|
||||||
|
|
||||||
#[cfg(feature = "migrations")]
|
#[cfg(feature = "migrations")]
|
||||||
use sea_orm_migration::MigratorTrait;
|
use sea_orm_migration::MigratorTrait;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use config::Config;
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use sea_orm::{ConnectOptions, Database};
|
use sea_orm::{ConnectOptions, Database};
|
||||||
|
@ -30,13 +31,17 @@ struct Args {
|
||||||
/// command to run
|
/// command to run
|
||||||
command: Mode,
|
command: Mode,
|
||||||
|
|
||||||
#[arg(short = 'd', long = "db", default_value = "sqlite://./upub.db")]
|
/// path to config file, leave empty to not use any
|
||||||
/// database connection uri
|
#[arg(short, long)]
|
||||||
database: String,
|
config: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(short = 'D', long, default_value = "http://localhost:3000")]
|
#[arg(long = "db")]
|
||||||
/// instance base domain, for AP ids
|
/// database connection uri, overrides config value
|
||||||
domain: String,
|
database: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
/// instance base domain, for AP ids, overrides config value
|
||||||
|
domain: Option<String>,
|
||||||
|
|
||||||
#[arg(long, default_value_t=false)]
|
#[arg(long, default_value_t=false)]
|
||||||
/// run with debug level tracing
|
/// run with debug level tracing
|
||||||
|
@ -48,6 +53,9 @@ enum Mode {
|
||||||
/// run fediverse server
|
/// run fediverse server
|
||||||
Serve,
|
Serve,
|
||||||
|
|
||||||
|
/// print current or default configuration
|
||||||
|
Config,
|
||||||
|
|
||||||
#[cfg(feature = "migrations")]
|
#[cfg(feature = "migrations")]
|
||||||
/// apply database migrations
|
/// apply database migrations
|
||||||
Migrate,
|
Migrate,
|
||||||
|
@ -71,11 +79,24 @@ async fn main() {
|
||||||
.with_max_level(if args.debug { tracing::Level::DEBUG } else { tracing::Level::INFO })
|
.with_max_level(if args.debug { tracing::Level::DEBUG } else { tracing::Level::INFO })
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
|
let config = Config::load(args.config);
|
||||||
|
|
||||||
|
let database = args.database.unwrap_or(config.datasource.connection_string.clone());
|
||||||
|
let domain = args.domain.unwrap_or(config.instance.domain.clone());
|
||||||
|
|
||||||
// TODO can i do connectoptions.into() or .connect() and skip these ugly bindings?
|
// TODO can i do connectoptions.into() or .connect() and skip these ugly bindings?
|
||||||
let mut opts = ConnectOptions::new(&args.database);
|
let mut opts = ConnectOptions::new(&database);
|
||||||
|
|
||||||
opts
|
opts
|
||||||
.sqlx_logging_level(tracing::log::LevelFilter::Debug);
|
.sqlx_logging_level(tracing::log::LevelFilter::Debug)
|
||||||
|
.max_connections(config.datasource.max_connections)
|
||||||
|
.min_connections(config.datasource.min_connections)
|
||||||
|
.acquire_timeout(std::time::Duration::from_secs(config.datasource.acquire_timeout_seconds))
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(config.datasource.connect_timeout_seconds))
|
||||||
|
.sqlx_slow_statements_logging_settings(
|
||||||
|
if config.datasource.slow_query_warn_enable { tracing::log::LevelFilter::Warn } else { tracing::log::LevelFilter::Off },
|
||||||
|
std::time::Duration::from_secs(config.datasource.slow_query_warn_seconds)
|
||||||
|
);
|
||||||
|
|
||||||
let db = Database::connect(opts)
|
let db = Database::connect(opts)
|
||||||
.await.expect("error connecting to db");
|
.await.expect("error connecting to db");
|
||||||
|
@ -88,11 +109,13 @@ async fn main() {
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
Mode::Cli { command } =>
|
Mode::Cli { command } =>
|
||||||
cli::run(command, db, args.domain)
|
cli::run(command, db, domain, config)
|
||||||
.await.expect("failed running cli task"),
|
.await.expect("failed running cli task"),
|
||||||
|
|
||||||
|
Mode::Config => println!("{}", toml::to_string_pretty(&config).expect("failed serializing config")),
|
||||||
|
|
||||||
Mode::Serve => {
|
Mode::Serve => {
|
||||||
let ctx = server::Context::new(db, args.domain)
|
let ctx = server::Context::new(db, domain, config)
|
||||||
.await.expect("failed creating server context");
|
.await.expect("failed creating server context");
|
||||||
|
|
||||||
use routes::activitypub::ActivityPubRouter;
|
use routes::activitypub::ActivityPubRouter;
|
||||||
|
|
|
@ -22,8 +22,8 @@ pub async fn view(
|
||||||
serde_json::Value::new_object()
|
serde_json::Value::new_object()
|
||||||
.set_id(Some(&url!(ctx, "")))
|
.set_id(Some(&url!(ctx, "")))
|
||||||
.set_actor_type(Some(apb::ActorType::Application))
|
.set_actor_type(Some(apb::ActorType::Application))
|
||||||
.set_name(Some("μpub"))
|
.set_name(Some(&ctx.cfg().instance.name))
|
||||||
.set_summary(Some("micro social network, federated"))
|
.set_summary(Some(&ctx.cfg().instance.description))
|
||||||
.set_inbox(apb::Node::link(url!(ctx, "/inbox")))
|
.set_inbox(apb::Node::link(url!(ctx, "/inbox")))
|
||||||
.set_outbox(apb::Node::link(url!(ctx, "/outbox")))
|
.set_outbox(apb::Node::link(url!(ctx, "/outbox")))
|
||||||
.set_published(Some(ctx.app().created))
|
.set_published(Some(ctx.app().created))
|
||||||
|
|
|
@ -73,8 +73,9 @@ pub async fn post(
|
||||||
Err(StatusCode::UNPROCESSABLE_ENTITY.into()) // won't ingest useless stuff
|
Err(StatusCode::UNPROCESSABLE_ENTITY.into()) // won't ingest useless stuff
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO emojireacts are NOT likes, but let's process them like ones for now maybe?
|
||||||
|
ActivityType::Like | ActivityType::EmojiReact => Ok(ctx.like(server, activity).await?),
|
||||||
ActivityType::Create => Ok(ctx.create(server, activity).await?),
|
ActivityType::Create => Ok(ctx.create(server, activity).await?),
|
||||||
ActivityType::Like => Ok(ctx.like(server, activity).await?),
|
|
||||||
ActivityType::Follow => Ok(ctx.follow(server, activity).await?),
|
ActivityType::Follow => Ok(ctx.follow(server, activity).await?),
|
||||||
ActivityType::Announce => Ok(ctx.announce(server, activity).await?),
|
ActivityType::Announce => Ok(ctx.announce(server, activity).await?),
|
||||||
ActivityType::Accept(_) => Ok(ctx.accept(server, activity).await?),
|
ActivityType::Accept(_) => Ok(ctx.accept(server, activity).await?),
|
||||||
|
|
|
@ -8,9 +8,9 @@ pub async fn get(
|
||||||
) -> crate::Result<Json<mastodon_async_entities::instance::Instance>> {
|
) -> crate::Result<Json<mastodon_async_entities::instance::Instance>> {
|
||||||
Ok(Json(mastodon_async_entities::instance::Instance {
|
Ok(Json(mastodon_async_entities::instance::Instance {
|
||||||
uri: ctx.domain().to_string(),
|
uri: ctx.domain().to_string(),
|
||||||
title: "μpub".to_string(),
|
title: ctx.cfg().instance.name.clone(),
|
||||||
description: "micro social network, federated".to_string(),
|
description: ctx.cfg().instance.description.clone(),
|
||||||
email: "me@alemi.dev".to_string(),
|
email: ctx.cfg().instance.contact.as_deref().unwrap_or_default().to_string(),
|
||||||
version: crate::VERSION.to_string(),
|
version: crate::VERSION.to_string(),
|
||||||
urls: None,
|
urls: None,
|
||||||
stats: None,
|
stats: None,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{collections::BTreeSet, sync::Arc};
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
||||||
|
|
||||||
use crate::{model, server::fetcher::Fetcher};
|
use crate::{config::Config, model, server::fetcher::Fetcher};
|
||||||
|
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ use super::dispatcher::Dispatcher;
|
||||||
pub struct Context(Arc<ContextInner>);
|
pub struct Context(Arc<ContextInner>);
|
||||||
struct ContextInner {
|
struct ContextInner {
|
||||||
db: DatabaseConnection,
|
db: DatabaseConnection,
|
||||||
|
config: Config,
|
||||||
domain: String,
|
domain: String,
|
||||||
protocol: String,
|
protocol: String,
|
||||||
dispatcher: Dispatcher,
|
dispatcher: Dispatcher,
|
||||||
|
@ -30,7 +31,7 @@ macro_rules! url {
|
||||||
impl Context {
|
impl Context {
|
||||||
|
|
||||||
// TODO slim constructor down, maybe make a builder?
|
// TODO slim constructor down, maybe make a builder?
|
||||||
pub async fn new(db: DatabaseConnection, mut domain: String) -> crate::Result<Self> {
|
pub async fn new(db: DatabaseConnection, mut domain: String, config: Config) -> crate::Result<Self> {
|
||||||
let protocol = if domain.starts_with("http://")
|
let protocol = if domain.starts_with("http://")
|
||||||
{ "http://" } else { "https://" }.to_string();
|
{ "http://" } else { "https://" }.to_string();
|
||||||
if domain.ends_with('/') {
|
if domain.ends_with('/') {
|
||||||
|
@ -71,7 +72,7 @@ impl Context {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Context(Arc::new(ContextInner {
|
Ok(Context(Arc::new(ContextInner {
|
||||||
db, domain, protocol, app, dispatcher,
|
db, domain, protocol, app, dispatcher, config,
|
||||||
relays: BTreeSet::from_iter(relays.into_iter()),
|
relays: BTreeSet::from_iter(relays.into_iter()),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -84,6 +85,10 @@ impl Context {
|
||||||
&self.0.db
|
&self.0.db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cfg(&self) -> &Config {
|
||||||
|
&self.0.config
|
||||||
|
}
|
||||||
|
|
||||||
pub fn domain(&self) -> &str {
|
pub fn domain(&self) -> &str {
|
||||||
&self.0.domain
|
&self.0.domain
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue