chore: BIG refactor into smaller crates
hopefully this makes lsp more responsive? because it wont need to recompile everything every time, but idk really
This commit is contained in:
parent
8c91b6c87a
commit
5b592874cb
88 changed files with 589 additions and 429 deletions
113
Cargo.lock
generated
113
Cargo.lock
generated
|
@ -4,9 +4,9 @@ version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.21.0"
|
version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gimli",
|
"gimli",
|
||||||
]
|
]
|
||||||
|
@ -310,9 +310,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.71"
|
version = "0.3.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
|
checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"addr2line",
|
"addr2line",
|
||||||
"cc",
|
"cc",
|
||||||
|
@ -1275,9 +1275,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.28.1"
|
version = "0.29.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
|
@ -1727,9 +1727,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d8d52be92d09acc2e01dddb7fde3ad983fc6489c7db4837e605bc3fca4cb63e"
|
checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
@ -2280,11 +2280,10 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.11"
|
version = "0.2.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"openssl",
|
"openssl",
|
||||||
|
@ -2407,9 +2406,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.32.2"
|
version = "0.35.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -4358,9 +4357,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.37.0"
|
version = "1.38.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
|
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -4377,9 +4376,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.2.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -4462,7 +4461,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"winnow 0.6.8",
|
"winnow 0.6.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4683,38 +4682,92 @@ name = "upub"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"apb",
|
"apb",
|
||||||
"async-recursion",
|
|
||||||
"axum",
|
"axum",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
|
||||||
"futures",
|
|
||||||
"jrd",
|
"jrd",
|
||||||
"mastodon-async-entities",
|
|
||||||
"mdhtml",
|
"mdhtml",
|
||||||
"nodeinfo",
|
"nodeinfo",
|
||||||
"openssl",
|
"openssl",
|
||||||
"rand",
|
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"sea-orm-migration",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde-inline-default",
|
"serde-inline-default",
|
||||||
"serde_default",
|
"serde_default",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha256",
|
"sha256",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"tower-http",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
|
||||||
"uriproxy",
|
"uriproxy",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "upub-bin"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"sea-orm",
|
||||||
|
"tokio",
|
||||||
|
"toml",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"upub",
|
||||||
|
"upub-cli",
|
||||||
|
"upub-migrations",
|
||||||
|
"upub-routes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "upub-cli"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"apb",
|
||||||
|
"chrono",
|
||||||
|
"clap",
|
||||||
|
"futures",
|
||||||
|
"openssl",
|
||||||
|
"sea-orm",
|
||||||
|
"serde_json",
|
||||||
|
"sha256",
|
||||||
|
"tracing",
|
||||||
|
"upub",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "upub-migrations"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"sea-orm-migration",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "upub-routes"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"apb",
|
||||||
|
"axum",
|
||||||
|
"chrono",
|
||||||
|
"jrd",
|
||||||
|
"mastodon-async-entities",
|
||||||
|
"nodeinfo",
|
||||||
|
"rand",
|
||||||
|
"reqwest",
|
||||||
|
"sea-orm",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha256",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tower-http",
|
||||||
|
"tracing",
|
||||||
|
"upub",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "upub-web"
|
name = "upub-web"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -4729,14 +4782,12 @@ dependencies = [
|
||||||
"leptos",
|
"leptos",
|
||||||
"leptos-use",
|
"leptos-use",
|
||||||
"leptos_router",
|
"leptos_router",
|
||||||
"log",
|
|
||||||
"mdhtml",
|
"mdhtml",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-inline-default",
|
"serde-inline-default",
|
||||||
"serde_default",
|
"serde_default",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
|
||||||
"tld",
|
"tld",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
@ -5177,9 +5228,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
|
checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
60
Cargo.toml
60
Cargo.toml
|
@ -1,8 +1,17 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["apb", "web", "utils/mdhtml", "utils/uriproxy"]
|
members = [
|
||||||
|
"apb",
|
||||||
|
"upub/core",
|
||||||
|
"upub/cli",
|
||||||
|
"upub/migrations",
|
||||||
|
"upub/routes",
|
||||||
|
"web",
|
||||||
|
"utils/mdhtml",
|
||||||
|
"utils/uriproxy"
|
||||||
|
]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "upub"
|
name = "upub-bin"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [ "alemi <me@alemi.dev>" ]
|
authors = [ "alemi <me@alemi.dev>" ]
|
||||||
|
@ -12,46 +21,25 @@ keywords = ["activitypub", "activitystreams", "json"]
|
||||||
repository = "https://git.alemi.dev/upub.git"
|
repository = "https://git.alemi.dev/upub.git"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[[bin]]
|
||||||
|
name = "upub"
|
||||||
|
path = "main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1"
|
|
||||||
rand = "0.8"
|
|
||||||
sha256 = "1.5"
|
|
||||||
openssl = "0.10" # TODO handle pubkeys with a smaller crate
|
|
||||||
base64 = "0.22"
|
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
|
||||||
uuid = { version = "1.8", features = ["v4"] }
|
|
||||||
regex = "1.10"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1"
|
|
||||||
serde_default = "0.1"
|
|
||||||
serde-inline-default = "0.2"
|
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
mdhtml = { path = "utils/mdhtml", features = ["markdown"] }
|
|
||||||
uriproxy = { path = "utils/uriproxy" }
|
|
||||||
jrd = "0.1"
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
sea-orm = "0.12"
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
futures = "0.3"
|
|
||||||
tokio = { version = "1.35", features = ["full"] } # TODO slim this down
|
tokio = { version = "1.35", features = ["full"] } # TODO slim this down
|
||||||
sea-orm = { version = "0.12", features = ["macros", "sqlx-sqlite", "runtime-tokio-rustls"] }
|
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
upub = { path = "upub/core" }
|
||||||
axum = "0.7"
|
upub-cli = { path = "upub/cli", optional = true }
|
||||||
tower-http = { version = "0.5", features = ["cors", "trace"] }
|
upub-migrations = { path = "upub/migrations", optional = true }
|
||||||
apb = { path = "apb", features = ["unstructured", "orm", "activitypub-fe", "activitypub-counters", "litepub", "ostatus", "toot"] }
|
upub-routes = { path = "upub/routes", optional = true }
|
||||||
# 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" }
|
|
||||||
# migrations
|
|
||||||
sea-orm-migration = { version = "0.12", optional = true }
|
|
||||||
# mastodon
|
|
||||||
mastodon-async-entities = { version = "1.1.0", optional = true }
|
|
||||||
time = { version = "0.3", features = ["serde"], optional = true }
|
|
||||||
async-recursion = "1.1"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mastodon", "migrations", "cli"]
|
default = ["serve", "migrate", "cli"]
|
||||||
cli = []
|
serve = ["dep:upub-routes"]
|
||||||
migrations = ["dep:sea-orm-migration"]
|
migrate = ["dep:upub-migrations"]
|
||||||
mastodon = ["dep:mastodon-async-entities", "dep:time"]
|
cli = ["dep:upub-cli"]
|
||||||
|
|
|
@ -125,3 +125,8 @@ pub use types::{
|
||||||
tombstone::{Tombstone, TombstoneMut},
|
tombstone::{Tombstone, TombstoneMut},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "unstructured")]
|
||||||
|
pub fn new() -> serde_json::Value {
|
||||||
|
serde_json::Value::Object(serde_json::Map::default())
|
||||||
|
}
|
||||||
|
|
|
@ -1,28 +1,16 @@
|
||||||
mod server;
|
|
||||||
mod model;
|
|
||||||
mod routes;
|
|
||||||
|
|
||||||
pub mod errors;
|
|
||||||
mod config;
|
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
|
||||||
mod cli;
|
|
||||||
|
|
||||||
#[cfg(feature = "migrations")]
|
|
||||||
mod migrations;
|
|
||||||
|
|
||||||
#[cfg(feature = "migrations")]
|
|
||||||
use sea_orm_migration::MigratorTrait;
|
|
||||||
use std::path::PathBuf;
|
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};
|
||||||
|
|
||||||
pub use errors::UpubResult as Result;
|
#[cfg(feature = "cli")]
|
||||||
use tower_http::{cors::CorsLayer, trace::TraceLayer};
|
use upub_cli as cli;
|
||||||
|
|
||||||
|
#[cfg(feature = "migrate")]
|
||||||
|
use upub_migrations as migrations;
|
||||||
|
|
||||||
|
#[cfg(feature = "serve")]
|
||||||
|
use upub_routes as routes;
|
||||||
|
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
/// all names were taken
|
/// all names were taken
|
||||||
|
@ -50,17 +38,10 @@ struct Args {
|
||||||
|
|
||||||
#[derive(Clone, Subcommand)]
|
#[derive(Clone, Subcommand)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
/// run fediverse server
|
|
||||||
Serve {
|
|
||||||
#[arg(short, long, default_value="127.0.0.1:3000")]
|
|
||||||
/// addr to bind and serve onto
|
|
||||||
bind: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// print current or default configuration
|
/// print current or default configuration
|
||||||
Config,
|
Config,
|
||||||
|
|
||||||
#[cfg(feature = "migrations")]
|
#[cfg(feature = "migrate")]
|
||||||
/// apply database migrations
|
/// apply database migrations
|
||||||
Migrate,
|
Migrate,
|
||||||
|
|
||||||
|
@ -71,6 +52,14 @@ enum Mode {
|
||||||
/// task to run
|
/// task to run
|
||||||
command: cli::CliCommand,
|
command: cli::CliCommand,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[cfg(feature = "serve")]
|
||||||
|
/// run fediverse server
|
||||||
|
Serve {
|
||||||
|
#[arg(short, long, default_value="127.0.0.1:3000")]
|
||||||
|
/// addr to bind and serve onto
|
||||||
|
bind: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -83,7 +72,7 @@ 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 config = upub::Config::load(args.config);
|
||||||
|
|
||||||
let database = args.database.unwrap_or(config.datasource.connection_string.clone());
|
let database = args.database.unwrap_or(config.datasource.connection_string.clone());
|
||||||
let domain = args.domain.unwrap_or(config.instance.domain.clone());
|
let domain = args.domain.unwrap_or(config.instance.domain.clone());
|
||||||
|
@ -106,40 +95,28 @@ async fn main() {
|
||||||
let db = Database::connect(opts)
|
let db = Database::connect(opts)
|
||||||
.await.expect("error connecting to db");
|
.await.expect("error connecting to db");
|
||||||
|
|
||||||
|
let ctx = upub::Context::new(db, domain, config.clone())
|
||||||
|
.await.expect("failed creating server context");
|
||||||
|
|
||||||
|
#[cfg(feature = "migrate")]
|
||||||
|
use migrations::MigratorTrait;
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
#[cfg(feature = "migrations")]
|
Mode::Config => println!("{}", toml::to_string_pretty(&config).expect("failed serializing config")),
|
||||||
|
|
||||||
|
#[cfg(feature = "migrate")]
|
||||||
Mode::Migrate =>
|
Mode::Migrate =>
|
||||||
migrations::Migrator::up(&db, None)
|
migrations::Migrator::up(ctx.db(), None)
|
||||||
.await.expect("error applying migrations"),
|
.await.expect("error applying migrations"),
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
Mode::Cli { command } =>
|
Mode::Cli { command } =>
|
||||||
cli::run(command, db, domain, config)
|
cli::run(ctx, command)
|
||||||
.await.expect("failed running cli task"),
|
.await.expect("failed running cli task"),
|
||||||
|
|
||||||
Mode::Config => println!("{}", toml::to_string_pretty(&config).expect("failed serializing config")),
|
#[cfg(feature = "serve")]
|
||||||
|
Mode::Serve { bind } =>
|
||||||
Mode::Serve { bind } => {
|
routes::serve(ctx, bind)
|
||||||
let ctx = server::Context::new(db, domain, config)
|
.await.expect("failed serving api routes"),
|
||||||
.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
|
|
||||||
.layer(CorsLayer::permissive())
|
|
||||||
.layer(TraceLayer::new_for_http())
|
|
||||||
.with_state(ctx);
|
|
||||||
|
|
||||||
// run our app with hyper, listening locally on port 3000
|
|
||||||
let listener = tokio::net::TcpListener::bind(bind)
|
|
||||||
.await.expect("could not bind tcp socket");
|
|
||||||
|
|
||||||
axum::serve(listener, router)
|
|
||||||
.await
|
|
||||||
.expect("failed serving application")
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,41 +0,0 @@
|
||||||
use sea_orm::{ActiveValue::{Set, NotSet}, ColumnTrait, EntityTrait, QueryFilter, QueryOrder};
|
|
||||||
|
|
||||||
use crate::server::addresser::Addresser;
|
|
||||||
|
|
||||||
pub async fn relay(ctx: crate::server::Context, actor: String, accept: bool) -> crate::Result<()> {
|
|
||||||
let aid = ctx.aid(&uuid::Uuid::new_v4().to_string());
|
|
||||||
|
|
||||||
let mut activity_model = crate::model::activity::ActiveModel {
|
|
||||||
internal: NotSet,
|
|
||||||
id: Set(aid.clone()),
|
|
||||||
activity_type: Set(apb::ActivityType::Follow),
|
|
||||||
actor: Set(ctx.base().to_string()),
|
|
||||||
object: Set(Some(actor.clone())),
|
|
||||||
target: Set(None),
|
|
||||||
published: Set(chrono::Utc::now()),
|
|
||||||
to: Set(crate::model::Audience(vec![actor.clone()])),
|
|
||||||
bto: Set(crate::model::Audience::default()),
|
|
||||||
cc: Set(crate::model::Audience(vec![apb::target::PUBLIC.to_string()])),
|
|
||||||
bcc: Set(crate::model::Audience::default()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if accept {
|
|
||||||
let follow_req = crate::model::activity::Entity::find()
|
|
||||||
.filter(crate::model::activity::Column::ActivityType.eq("Follow"))
|
|
||||||
.filter(crate::model::activity::Column::Actor.eq(&actor))
|
|
||||||
.filter(crate::model::activity::Column::Object.eq(ctx.base()))
|
|
||||||
.order_by_desc(crate::model::activity::Column::Published)
|
|
||||||
.one(ctx.db())
|
|
||||||
.await?
|
|
||||||
.expect("no follow request to accept");
|
|
||||||
activity_model.activity_type = Set(apb::ActivityType::Accept(apb::AcceptType::Accept));
|
|
||||||
activity_model.object = Set(Some(follow_req.id));
|
|
||||||
};
|
|
||||||
|
|
||||||
crate::model::activity::Entity::insert(activity_model)
|
|
||||||
.exec(ctx.db()).await?;
|
|
||||||
|
|
||||||
ctx.dispatch(ctx.base(), vec![actor, apb::target::PUBLIC.to_string()], &aid, None).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
pub mod activitypub;
|
|
||||||
|
|
||||||
#[cfg(feature = "web")]
|
|
||||||
pub mod web;
|
|
||||||
|
|
||||||
#[cfg(feature = "mastodon")]
|
|
||||||
pub mod mastodon;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "mastodon"))]
|
|
||||||
pub mod mastodon {
|
|
||||||
pub trait MastodonRouter {
|
|
||||||
fn mastodon_routes(self) -> Self where Self: Sized { self }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MastodonRouter for axum::Router<crate::server::Context> {}
|
|
||||||
}
|
|
24
upub/cli/Cargo.toml
Normal file
24
upub/cli/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "upub-cli"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = [ "alemi <me@alemi.dev>" ]
|
||||||
|
description = "cli maintenance tasks for upub"
|
||||||
|
license = "AGPL-3.0"
|
||||||
|
repository = "https://git.alemi.dev/upub.git"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
apb = { path = "../../apb/" }
|
||||||
|
upub = { path = "../core" }
|
||||||
|
tracing = "0.1"
|
||||||
|
serde_json = "1"
|
||||||
|
sha256 = "1.5"
|
||||||
|
uuid = { version = "1.8", features = ["v4"] }
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
openssl = "0.10" # TODO handle pubkeys with a smaller crate
|
||||||
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
|
sea-orm = { version = "0.12", features = ["macros", "sqlx-sqlite", "runtime-tokio-rustls"] }
|
||||||
|
futures = "0.3"
|
1
upub/cli/README.md
Normal file
1
upub/cli/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# upub cli
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::model::{addressing, config, credential, activity, object, actor, Audience};
|
use upub::model::{addressing, config, credential, activity, object, actor, Audience};
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use sea_orm::{ActiveValue::NotSet, IntoActiveModel};
|
use sea_orm::{ActiveValue::NotSet, IntoActiveModel};
|
||||||
|
|
||||||
pub async fn faker(ctx: crate::server::Context, count: i64) -> Result<(), sea_orm::DbErr> {
|
pub async fn faker(ctx: upub::Context, count: i64) -> Result<(), sea_orm::DbErr> {
|
||||||
use sea_orm::{EntityTrait, Set};
|
use sea_orm::{EntityTrait, Set};
|
||||||
|
|
||||||
let domain = ctx.domain();
|
let domain = ctx.domain();
|
|
@ -1,23 +1,22 @@
|
||||||
use sea_orm::EntityTrait;
|
use sea_orm::EntityTrait;
|
||||||
|
use upub::server::{fetcher::Fetchable, normalizer::Normalizer};
|
||||||
|
|
||||||
use crate::server::{fetcher::Fetchable, normalizer::Normalizer, Context};
|
pub async fn fetch(ctx: upub::Context, uri: String, save: bool) -> upub::Result<()> {
|
||||||
|
|
||||||
pub async fn fetch(ctx: crate::server::Context, uri: String, save: bool) -> crate::Result<()> {
|
|
||||||
use apb::Base;
|
use apb::Base;
|
||||||
|
|
||||||
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?;
|
||||||
|
|
||||||
let obj = node.extract().expect("node still empty after fetch?");
|
let obj = node.extract().expect("node still empty after fetch?");
|
||||||
let server = Context::server(&uri);
|
let server = upub::Context::server(&uri);
|
||||||
|
|
||||||
println!("{}", serde_json::to_string_pretty(&obj).unwrap());
|
println!("{}", serde_json::to_string_pretty(&obj).unwrap());
|
||||||
|
|
||||||
if save {
|
if save {
|
||||||
match obj.base_type() {
|
match obj.base_type() {
|
||||||
Some(apb::BaseType::Object(apb::ObjectType::Actor(_))) => {
|
Some(apb::BaseType::Object(apb::ObjectType::Actor(_))) => {
|
||||||
crate::model::actor::Entity::insert(
|
upub::model::actor::Entity::insert(
|
||||||
crate::model::actor::ActiveModel::new(&obj).unwrap()
|
upub::model::actor::ActiveModel::new(&obj).unwrap()
|
||||||
).exec(ctx.db()).await.unwrap();
|
).exec(ctx.db()).await.unwrap();
|
||||||
},
|
},
|
||||||
Some(apb::BaseType::Object(apb::ObjectType::Activity(_))) => {
|
Some(apb::BaseType::Object(apb::ObjectType::Activity(_))) => {
|
|
@ -1,7 +1,6 @@
|
||||||
use sea_orm::EntityTrait;
|
use sea_orm::EntityTrait;
|
||||||
|
|
||||||
|
pub async fn fix(ctx: upub::Context, likes: bool, shares: bool, replies: bool) -> upub::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();
|
let db = ctx.db();
|
||||||
|
|
||||||
|
@ -9,19 +8,19 @@ pub async fn fix(ctx: crate::server::Context, likes: bool, shares: bool, replies
|
||||||
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 = upub::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.object, store.get(&like.object).unwrap_or(&0) + 1);
|
store.insert(like.object, store.get(&like.object).unwrap_or(&0) + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (k, v) in store {
|
for (k, v) in store {
|
||||||
let m = crate::model::object::ActiveModel {
|
let m = upub::model::object::ActiveModel {
|
||||||
internal: sea_orm::Set(k),
|
internal: sea_orm::Set(k),
|
||||||
likes: sea_orm::Set(v),
|
likes: sea_orm::Set(v),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
if let Err(e) = crate::model::object::Entity::update(m)
|
if let Err(e) = upub::model::object::Entity::update(m)
|
||||||
.exec(db)
|
.exec(db)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -34,19 +33,19 @@ pub async fn fix(ctx: crate::server::Context, likes: bool, shares: bool, replies
|
||||||
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::announce::Entity::find().stream(db).await?;
|
let mut stream = upub::model::announce::Entity::find().stream(db).await?;
|
||||||
while let Some(share) = stream.try_next().await? {
|
while let Some(share) = stream.try_next().await? {
|
||||||
store.insert(share.object, store.get(&share.object).unwrap_or(&0) + 1);
|
store.insert(share.object, store.get(&share.object).unwrap_or(&0) + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (k, v) in store {
|
for (k, v) in store {
|
||||||
let m = crate::model::object::ActiveModel {
|
let m = upub::model::object::ActiveModel {
|
||||||
internal: sea_orm::Set(k),
|
internal: sea_orm::Set(k),
|
||||||
announces: sea_orm::Set(v),
|
announces: sea_orm::Set(v),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
if let Err(e) = crate::model::object::Entity::update(m)
|
if let Err(e) = upub::model::object::Entity::update(m)
|
||||||
.exec(db)
|
.exec(db)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -59,7 +58,7 @@ pub async fn fix(ctx: crate::server::Context, likes: bool, shares: bool, replies
|
||||||
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 = upub::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);
|
||||||
|
@ -69,12 +68,12 @@ pub async fn fix(ctx: crate::server::Context, likes: bool, shares: bool, replies
|
||||||
}
|
}
|
||||||
|
|
||||||
for (k, v) in store {
|
for (k, v) in store {
|
||||||
let m = crate::model::object::ActiveModel {
|
let m = upub::model::object::ActiveModel {
|
||||||
id: sea_orm::Set(k.clone()),
|
id: sea_orm::Set(k.clone()),
|
||||||
replies: sea_orm::Set(v),
|
replies: sea_orm::Set(v),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
if let Err(e) = crate::model::object::Entity::update(m)
|
if let Err(e) = upub::model::object::Entity::update(m)
|
||||||
.exec(db)
|
.exec(db)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
|
@ -93,15 +93,7 @@ pub enum CliCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(
|
pub async fn run(ctx: upub::Context, command: CliCommand) -> upub::Result<()> {
|
||||||
command: CliCommand,
|
|
||||||
db: sea_orm::DatabaseConnection,
|
|
||||||
domain: String,
|
|
||||||
config: crate::config::Config,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let ctx = crate::server::Context::new(
|
|
||||||
db, domain, config,
|
|
||||||
).await?;
|
|
||||||
match command {
|
match command {
|
||||||
CliCommand::Faker { count } =>
|
CliCommand::Faker { count } =>
|
||||||
Ok(faker(ctx, count as i64).await?),
|
Ok(faker(ctx, count as i64).await?),
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::server::admin::Administrable;
|
use upub::server::admin::Administrable;
|
||||||
|
|
||||||
pub async fn register(
|
pub async fn register(
|
||||||
ctx: crate::server::Context,
|
ctx: upub::Context,
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
display_name: Option<String>,
|
display_name: Option<String>,
|
||||||
summary: Option<String>,
|
summary: Option<String>,
|
||||||
avatar_url: Option<String>,
|
avatar_url: Option<String>,
|
||||||
banner_url: Option<String>,
|
banner_url: Option<String>,
|
||||||
) -> crate::Result<()> {
|
) -> upub::Result<()> {
|
||||||
ctx.register_user(
|
ctx.register_user(
|
||||||
username.clone(),
|
username.clone(),
|
||||||
password,
|
password,
|
41
upub/cli/src/relay.rs
Normal file
41
upub/cli/src/relay.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use sea_orm::{ActiveValue::{Set, NotSet}, ColumnTrait, EntityTrait, QueryFilter, QueryOrder};
|
||||||
|
|
||||||
|
use upub::server::addresser::Addresser;
|
||||||
|
|
||||||
|
pub async fn relay(ctx: upub::Context, actor: String, accept: bool) -> upub::Result<()> {
|
||||||
|
let aid = ctx.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
|
|
||||||
|
let mut activity_model = upub::model::activity::ActiveModel {
|
||||||
|
internal: NotSet,
|
||||||
|
id: Set(aid.clone()),
|
||||||
|
activity_type: Set(apb::ActivityType::Follow),
|
||||||
|
actor: Set(ctx.base().to_string()),
|
||||||
|
object: Set(Some(actor.clone())),
|
||||||
|
target: Set(None),
|
||||||
|
published: Set(chrono::Utc::now()),
|
||||||
|
to: Set(upub::model::Audience(vec![actor.clone()])),
|
||||||
|
bto: Set(upub::model::Audience::default()),
|
||||||
|
cc: Set(upub::model::Audience(vec![apb::target::PUBLIC.to_string()])),
|
||||||
|
bcc: Set(upub::model::Audience::default()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if accept {
|
||||||
|
let follow_req = upub::model::activity::Entity::find()
|
||||||
|
.filter(upub::model::activity::Column::ActivityType.eq("Follow"))
|
||||||
|
.filter(upub::model::activity::Column::Actor.eq(&actor))
|
||||||
|
.filter(upub::model::activity::Column::Object.eq(ctx.base()))
|
||||||
|
.order_by_desc(upub::model::activity::Column::Published)
|
||||||
|
.one(ctx.db())
|
||||||
|
.await?
|
||||||
|
.expect("no follow request to accept");
|
||||||
|
activity_model.activity_type = Set(apb::ActivityType::Accept(apb::AcceptType::Accept));
|
||||||
|
activity_model.object = Set(Some(follow_req.id));
|
||||||
|
};
|
||||||
|
|
||||||
|
upub::model::activity::Entity::insert(activity_model)
|
||||||
|
.exec(ctx.db()).await?;
|
||||||
|
|
||||||
|
ctx.dispatch(ctx.base(), vec![actor, apb::target::PUBLIC.to_string()], &aid, None).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,15 +1,15 @@
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use sea_orm::{ActiveValue::Set, ColumnTrait, EntityTrait, QueryFilter};
|
use sea_orm::{ActiveValue::Set, ColumnTrait, EntityTrait, QueryFilter};
|
||||||
|
|
||||||
use crate::server::fetcher::Fetcher;
|
use upub::server::fetcher::Fetcher;
|
||||||
|
|
||||||
pub async fn update_users(ctx: crate::server::Context, days: i64) -> crate::Result<()> {
|
pub async fn update_users(ctx: upub::Context, days: i64) -> upub::Result<()> {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut insertions = Vec::new();
|
let mut insertions = Vec::new();
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut stream = crate::model::actor::Entity::find()
|
let mut stream = upub::model::actor::Entity::find()
|
||||||
.filter(crate::model::actor::Column::Updated.lt(chrono::Utc::now() - chrono::Duration::days(days)))
|
.filter(upub::model::actor::Column::Updated.lt(chrono::Utc::now() - chrono::Duration::days(days)))
|
||||||
.stream(ctx.db())
|
.stream(ctx.db())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ pub async fn update_users(ctx: crate::server::Context, days: i64) -> crate::Resu
|
||||||
match ctx.pull(&user.id).await.map(|x| x.actor()) {
|
match ctx.pull(&user.id).await.map(|x| x.actor()) {
|
||||||
Err(e) => tracing::warn!("could not update user {}: {e}", user.id),
|
Err(e) => tracing::warn!("could not update user {}: {e}", user.id),
|
||||||
Ok(Err(e)) => tracing::warn!("could not update user {}: {e}", user.id),
|
Ok(Err(e)) => tracing::warn!("could not update user {}: {e}", user.id),
|
||||||
Ok(Ok(doc)) => match crate::model::actor::ActiveModel::new(&doc) {
|
Ok(Ok(doc)) => match upub::model::actor::ActiveModel::new(&doc) {
|
||||||
Ok(mut u) => {
|
Ok(mut u) => {
|
||||||
u.internal = Set(user.internal);
|
u.internal = Set(user.internal);
|
||||||
u.updated = Set(chrono::Utc::now());
|
u.updated = Set(chrono::Utc::now());
|
||||||
|
@ -34,7 +34,7 @@ pub async fn update_users(ctx: crate::server::Context, days: i64) -> crate::Resu
|
||||||
|
|
||||||
for (uid, user_model) in insertions {
|
for (uid, user_model) in insertions {
|
||||||
tracing::info!("updating user {}", uid);
|
tracing::info!("updating user {}", uid);
|
||||||
crate::model::actor::Entity::update(user_model)
|
upub::model::actor::Entity::update(user_model)
|
||||||
.exec(ctx.db())
|
.exec(ctx.db())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
36
upub/core/Cargo.toml
Normal file
36
upub/core/Cargo.toml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
[package]
|
||||||
|
name = "upub"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = [ "alemi <me@alemi.dev>" ]
|
||||||
|
description = "core inner workings of upub"
|
||||||
|
license = "AGPL-3.0"
|
||||||
|
repository = "https://git.alemi.dev/upub.git"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = "1"
|
||||||
|
sha256 = "1.5"
|
||||||
|
openssl = "0.10" # TODO handle pubkeys with a smaller crate
|
||||||
|
base64 = "0.22"
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
uuid = { version = "1.8", features = ["v4"] }
|
||||||
|
regex = "1.10"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
serde_default = "0.1"
|
||||||
|
serde-inline-default = "0.2"
|
||||||
|
toml = "0.8"
|
||||||
|
mdhtml = { path = "../../utils/mdhtml", features = ["markdown"] }
|
||||||
|
uriproxy = { path = "../../utils/uriproxy" }
|
||||||
|
jrd = "0.1"
|
||||||
|
tracing = "0.1"
|
||||||
|
tokio = { version = "1.35", features = ["full"] } # TODO slim this down
|
||||||
|
sea-orm = { version = "0.12", features = ["macros", "sqlx-sqlite", "runtime-tokio-rustls"] }
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
axum = "0.7"
|
||||||
|
apb = { path = "../../apb", features = ["unstructured", "orm", "activitypub-fe", "activitypub-counters", "litepub", "ostatus", "toot"] }
|
||||||
|
# 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" }
|
1
upub/core/README.md
Normal file
1
upub/core/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# upub core
|
|
@ -31,6 +31,9 @@ pub enum UpubError {
|
||||||
#[error("type mismatch on object: expected {0:?}, found {1:?}")]
|
#[error("type mismatch on object: expected {0:?}, found {1:?}")]
|
||||||
Mismatch(apb::ObjectType, apb::ObjectType),
|
Mismatch(apb::ObjectType, apb::ObjectType),
|
||||||
|
|
||||||
|
#[error("os I/O error: {0}")]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
|
||||||
// TODO this isn't really an error but i need to redirect from some routes so this allows me to
|
// TODO this isn't really an error but i need to redirect from some routes so this allows me to
|
||||||
// keep the type hints on the return type, still what the hell!!!!
|
// keep the type hints on the return type, still what the hell!!!!
|
||||||
#[error("redirecting to {0}")]
|
#[error("redirecting to {0}")]
|
19
upub/core/src/ext.rs
Normal file
19
upub/core/src/ext.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
#[axum::async_trait]
|
||||||
|
pub trait AnyQuery {
|
||||||
|
async fn any(self, db: &sea_orm::DatabaseConnection) -> crate::Result<bool>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::async_trait]
|
||||||
|
impl<T : sea_orm::EntityTrait> AnyQuery for sea_orm::Select<T> {
|
||||||
|
async fn any(self, db: &sea_orm::DatabaseConnection) -> crate::Result<bool> {
|
||||||
|
Ok(self.one(db).await?.is_some())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::async_trait]
|
||||||
|
impl<T : sea_orm::SelectorTrait + Send> AnyQuery for sea_orm::Selector<T> {
|
||||||
|
async fn any(self, db: &sea_orm::DatabaseConnection) -> crate::Result<bool> {
|
||||||
|
Ok(self.one(db).await?.is_some())
|
||||||
|
}
|
||||||
|
}
|
12
upub/core/src/lib.rs
Normal file
12
upub/core/src/lib.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
pub mod config;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod server;
|
||||||
|
pub mod model;
|
||||||
|
pub mod ext;
|
||||||
|
|
||||||
|
pub use server::Context;
|
||||||
|
pub use config::Config;
|
||||||
|
pub use errors::UpubResult as Result;
|
||||||
|
pub use errors::UpubError as Error;
|
||||||
|
|
||||||
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
@ -1,7 +1,7 @@
|
||||||
use apb::{ActivityMut, ActivityType, BaseMut, ObjectMut};
|
use apb::{ActivityMut, ActivityType, BaseMut, ObjectMut};
|
||||||
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::{model::Audience, errors::UpubError, routes::activitypub::jsonld::LD};
|
use crate::{model::Audience, errors::UpubError};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "activities")]
|
#[sea_orm(table_name = "activities")]
|
||||||
|
@ -109,7 +109,7 @@ impl ActiveModel {
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn ap(self) -> serde_json::Value {
|
pub fn ap(self) -> serde_json::Value {
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_id(Some(&self.id))
|
.set_id(Some(&self.id))
|
||||||
.set_activity_type(Some(self.activity_type))
|
.set_activity_type(Some(self.activity_type))
|
||||||
.set_actor(apb::Node::link(self.actor))
|
.set_actor(apb::Node::link(self.actor))
|
|
@ -2,7 +2,7 @@ use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use apb::{Actor, ActorMut, ActorType, BaseMut, DocumentMut, Endpoints, EndpointsMut, Object, ObjectMut, PublicKey, PublicKeyMut};
|
use apb::{Actor, ActorMut, ActorType, BaseMut, DocumentMut, Endpoints, EndpointsMut, Object, ObjectMut, PublicKey, PublicKeyMut};
|
||||||
|
|
||||||
use crate::{errors::UpubError, routes::activitypub::jsonld::LD};
|
use crate::errors::UpubError;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "actors")]
|
#[sea_orm(table_name = "actors")]
|
||||||
|
@ -193,18 +193,18 @@ impl ActiveModel {
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn ap(self) -> serde_json::Value {
|
pub fn ap(self) -> serde_json::Value {
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_id(Some(&self.id))
|
.set_id(Some(&self.id))
|
||||||
.set_actor_type(Some(self.actor_type))
|
.set_actor_type(Some(self.actor_type))
|
||||||
.set_name(self.name.as_deref())
|
.set_name(self.name.as_deref())
|
||||||
.set_summary(self.summary.as_deref())
|
.set_summary(self.summary.as_deref())
|
||||||
.set_icon(apb::Node::maybe_object(self.icon.map(|i|
|
.set_icon(apb::Node::maybe_object(self.icon.map(|i|
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_document_type(Some(apb::DocumentType::Image))
|
.set_document_type(Some(apb::DocumentType::Image))
|
||||||
.set_url(apb::Node::link(i.clone()))
|
.set_url(apb::Node::link(i.clone()))
|
||||||
)))
|
)))
|
||||||
.set_image(apb::Node::maybe_object(self.image.map(|i|
|
.set_image(apb::Node::maybe_object(self.image.map(|i|
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_document_type(Some(apb::DocumentType::Image))
|
.set_document_type(Some(apb::DocumentType::Image))
|
||||||
.set_url(apb::Node::link(i.clone()))
|
.set_url(apb::Node::link(i.clone()))
|
||||||
)))
|
)))
|
||||||
|
@ -218,13 +218,13 @@ impl Model {
|
||||||
.set_following(apb::Node::maybe_link(self.following))
|
.set_following(apb::Node::maybe_link(self.following))
|
||||||
.set_followers(apb::Node::maybe_link(self.followers))
|
.set_followers(apb::Node::maybe_link(self.followers))
|
||||||
.set_public_key(apb::Node::object(
|
.set_public_key(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_id(Some(&format!("{}#main-key", self.id)))
|
.set_id(Some(&format!("{}#main-key", self.id)))
|
||||||
.set_owner(Some(&self.id))
|
.set_owner(Some(&self.id))
|
||||||
.set_public_key_pem(&self.public_key)
|
.set_public_key_pem(&self.public_key)
|
||||||
))
|
))
|
||||||
.set_endpoints(apb::Node::object(
|
.set_endpoints(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_shared_inbox(self.shared_inbox.as_deref())
|
.set_shared_inbox(self.shared_inbox.as_deref())
|
||||||
))
|
))
|
||||||
.set_discoverable(Some(true))
|
.set_discoverable(Some(true))
|
|
@ -1,8 +1,6 @@
|
||||||
use apb::{ActivityMut, ObjectMut};
|
use apb::{ActivityMut, ObjectMut};
|
||||||
use sea_orm::{entity::prelude::*, sea_query::IntoCondition, Condition, FromQueryResult, Iterable, Order, QueryOrder, QuerySelect, SelectColumns};
|
use sea_orm::{entity::prelude::*, sea_query::IntoCondition, Condition, FromQueryResult, Iterable, Order, QueryOrder, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::routes::activitypub::jsonld::LD;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "addressing")]
|
#[sea_orm(table_name = "addressing")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
|
@ -121,17 +119,17 @@ impl Event {
|
||||||
.set_attachment(attachment)
|
.set_attachment(attachment)
|
||||||
.set_liked_by_me(if liked.is_some() { Some(true) } else { None })
|
.set_liked_by_me(if liked.is_some() { Some(true) } else { None })
|
||||||
)),
|
)),
|
||||||
Event::StrayObject { object, liked } => serde_json::Value::new_object()
|
Event::StrayObject { object, liked } => apb::new()
|
||||||
.set_activity_type(Some(apb::ActivityType::Activity))
|
.set_activity_type(Some(apb::ActivityType::Activity))
|
||||||
.set_object(apb::Node::object(
|
.set_object(apb::Node::object(
|
||||||
object.ap()
|
object.ap()
|
||||||
.set_attachment(attachment)
|
.set_attachment(attachment)
|
||||||
.set_liked_by_me(if liked.is_some() { Some(true) } else { None })
|
.set_liked_by_me(if liked.is_some() { Some(true) } else { None })
|
||||||
)),
|
)),
|
||||||
Event::Tombstone => serde_json::Value::new_object()
|
Event::Tombstone => apb::new()
|
||||||
.set_activity_type(Some(apb::ActivityType::Activity))
|
.set_activity_type(Some(apb::ActivityType::Activity))
|
||||||
.set_object(apb::Node::object(
|
.set_object(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_object_type(Some(apb::ObjectType::Tombstone))
|
.set_object_type(Some(apb::ObjectType::Tombstone))
|
||||||
)),
|
)),
|
||||||
}
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
use apb::{DocumentMut, DocumentType, ObjectMut};
|
use apb::{DocumentMut, DocumentType, ObjectMut};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
use crate::routes::activitypub::jsonld::LD;
|
|
||||||
|
|
||||||
use super::addressing::Event;
|
use super::addressing::Event;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
@ -41,7 +39,7 @@ impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn ap(self) -> serde_json::Value {
|
pub fn ap(self) -> serde_json::Value {
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_url(apb::Node::link(self.url))
|
.set_url(apb::Node::link(self.url))
|
||||||
.set_document_type(Some(self.document_type))
|
.set_document_type(Some(self.document_type))
|
||||||
.set_media_type(Some(&self.media_type))
|
.set_media_type(Some(&self.media_type))
|
|
@ -1,7 +1,7 @@
|
||||||
use apb::{BaseMut, Collection, CollectionMut, ObjectMut, ObjectType};
|
use apb::{BaseMut, Collection, CollectionMut, ObjectMut, ObjectType};
|
||||||
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::{errors::UpubError, routes::activitypub::jsonld::LD};
|
use crate::errors::UpubError;
|
||||||
|
|
||||||
use super::Audience;
|
use super::Audience;
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ impl ActiveModel {
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn ap(self) -> serde_json::Value {
|
pub fn ap(self) -> serde_json::Value {
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_id(Some(&self.id))
|
.set_id(Some(&self.id))
|
||||||
.set_object_type(Some(self.object_type))
|
.set_object_type(Some(self.object_type))
|
||||||
.set_attributed_to(apb::Node::maybe_link(self.attributed_to))
|
.set_attributed_to(apb::Node::maybe_link(self.attributed_to))
|
||||||
|
@ -204,17 +204,17 @@ impl Model {
|
||||||
.set_url(apb::Node::maybe_link(self.url))
|
.set_url(apb::Node::maybe_link(self.url))
|
||||||
.set_sensitive(Some(self.sensitive))
|
.set_sensitive(Some(self.sensitive))
|
||||||
.set_shares(apb::Node::object(
|
.set_shares(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
||||||
.set_total_items(Some(self.announces as u64))
|
.set_total_items(Some(self.announces as u64))
|
||||||
))
|
))
|
||||||
.set_likes(apb::Node::object(
|
.set_likes(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
||||||
.set_total_items(Some(self.likes as u64))
|
.set_total_items(Some(self.likes as u64))
|
||||||
))
|
))
|
||||||
.set_replies(apb::Node::object(
|
.set_replies(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
||||||
.set_total_items(Some(self.replies as u64))
|
.set_total_items(Some(self.replies as u64))
|
||||||
))
|
))
|
|
@ -2,10 +2,10 @@ use std::{collections::BTreeSet, sync::Arc};
|
||||||
|
|
||||||
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns};
|
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::{config::Config, errors::UpubError, model};
|
use crate::{config::Config, errors::UpubError, model, ext::AnyQuery};
|
||||||
use uriproxy::UriClass;
|
use uriproxy::UriClass;
|
||||||
|
|
||||||
use super::{builders::AnyQuery, dispatcher::Dispatcher};
|
use super::dispatcher::Dispatcher;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
|
@ -3,7 +3,7 @@ use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, Order, QueryFilter,
|
||||||
use tokio::{sync::broadcast, task::JoinHandle};
|
use tokio::{sync::broadcast, task::JoinHandle};
|
||||||
|
|
||||||
use apb::{ActivityMut, Node};
|
use apb::{ActivityMut, Node};
|
||||||
use crate::{model, routes::activitypub::jsonld::LD, server::{fetcher::Fetcher, Context}};
|
use crate::{model, Context, server::{fetcher::Fetcher, jsonld::LD}};
|
||||||
|
|
||||||
pub struct Dispatcher {
|
pub struct Dispatcher {
|
||||||
waker: broadcast::Sender<()>,
|
waker: broadcast::Sender<()>,
|
|
@ -2,7 +2,7 @@ use apb::{target::Addressed, Activity, Base, Object};
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use sea_orm::{sea_query::Expr, ActiveValue::{Set, NotSet}, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, SelectColumns};
|
use sea_orm::{sea_query::Expr, ActiveValue::{Set, NotSet}, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::{errors::{LoggableError, UpubError}, model, server::{addresser::Addresser, builders::AnyQuery, normalizer::Normalizer}};
|
use crate::{errors::{LoggableError, UpubError}, model, ext::AnyQuery, server::{addresser::Addresser, normalizer::Normalizer}};
|
||||||
|
|
||||||
use super::{fetcher::{Fetcher, PullResult}, side_effects::SideEffects, Context};
|
use super::{fetcher::{Fetcher, PullResult}, side_effects::SideEffects, Context};
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
// TODO
|
|
||||||
// move this file somewhere else
|
|
||||||
// it's not a route
|
|
||||||
// maybe under src/server/jsonld.rs ??
|
|
||||||
|
|
||||||
use apb::Object;
|
use apb::Object;
|
||||||
use axum::response::{IntoResponse, Response};
|
|
||||||
|
|
||||||
pub trait LD {
|
pub trait LD {
|
||||||
fn ld_context(self) -> Self;
|
fn ld_context(self) -> Self;
|
||||||
fn new_object() -> serde_json::Value {
|
|
||||||
serde_json::Value::Object(serde_json::Map::default())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LD for serde_json::Value {
|
impl LD for serde_json::Value {
|
||||||
|
@ -51,15 +43,3 @@ impl LD for serde_json::Value {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// got this from https://github.com/kitsune-soc/kitsune/blob/b023a12b687dd9a274233a5a9950f2de5e192344/kitsune/src/http/responder.rs
|
|
||||||
// i was trying to do it with middlewares but this is way cleaner
|
|
||||||
pub struct JsonLD<T>(pub T);
|
|
||||||
impl<T: serde::Serialize> IntoResponse for JsonLD<T> {
|
|
||||||
fn into_response(self) -> Response {
|
|
||||||
(
|
|
||||||
[("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")],
|
|
||||||
axum::Json(self.0)
|
|
||||||
).into_response()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,9 +7,9 @@ pub mod inbox;
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod outbox;
|
pub mod outbox;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod builders;
|
|
||||||
pub mod httpsign;
|
pub mod httpsign;
|
||||||
pub mod normalizer;
|
pub mod normalizer;
|
||||||
pub mod side_effects;
|
pub mod side_effects;
|
||||||
|
pub mod jsonld;
|
||||||
|
|
||||||
pub use context::Context;
|
pub use context::Context;
|
|
@ -2,9 +2,9 @@ use apb::{target::Addressed, Activity, ActivityMut, Base, BaseMut, Node, Object,
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use sea_orm::{sea_query::Expr, ActiveValue::{Set, NotSet, Unchanged}, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, QuerySelect, SelectColumns};
|
use sea_orm::{sea_query::Expr, ActiveValue::{Set, NotSet, Unchanged}, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::{errors::UpubError, model, routes::activitypub::jsonld::LD};
|
use crate::{errors::UpubError, model, ext::AnyQuery};
|
||||||
|
|
||||||
use super::{addresser::Addresser, builders::AnyQuery, fetcher::Fetcher, normalizer::Normalizer, side_effects::SideEffects, Context};
|
use super::{addresser::Addresser, fetcher::Fetcher, normalizer::Normalizer, side_effects::SideEffects, Context};
|
||||||
|
|
||||||
|
|
||||||
#[axum::async_trait]
|
#[axum::async_trait]
|
||||||
|
@ -16,7 +16,7 @@ impl apb::server::Outbox for Context {
|
||||||
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> {
|
||||||
self.create(
|
self.create(
|
||||||
uid,
|
uid,
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_activity_type(Some(apb::ActivityType::Create))
|
.set_activity_type(Some(apb::ActivityType::Create))
|
||||||
.set_to(object.to())
|
.set_to(object.to())
|
||||||
.set_bto(object.bto())
|
.set_bto(object.bto())
|
14
upub/migrations/Cargo.toml
Normal file
14
upub/migrations/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "upub-migrations"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = [ "alemi <me@alemi.dev>" ]
|
||||||
|
description = "database migrations for upub"
|
||||||
|
license = "AGPL-3.0"
|
||||||
|
repository = "https://git.alemi.dev/upub.git"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sea-orm-migration = "0.12"
|
1
upub/migrations/README.md
Normal file
1
upub/migrations/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# upub migrations
|
|
@ -22,3 +22,5 @@ impl MigratorTrait for Migrator {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use sea_orm_migration::MigratorTrait;
|
32
upub/routes/Cargo.toml
Normal file
32
upub/routes/Cargo.toml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
[package]
|
||||||
|
name = "upub-routes"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = [ "alemi <me@alemi.dev>" ]
|
||||||
|
description = "api route definitions for upub"
|
||||||
|
license = "AGPL-3.0"
|
||||||
|
repository = "https://git.alemi.dev/upub.git"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.8"
|
||||||
|
sha256 = "1.5"
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
upub = { path = "../core/" }
|
||||||
|
jrd = "0.1"
|
||||||
|
tracing = "0.1"
|
||||||
|
tokio = { version = "1.35", features = ["full"] } # TODO slim this down
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
axum = "0.7"
|
||||||
|
tower-http = { version = "0.5", features = ["cors", "trace"] }
|
||||||
|
apb = { path = "../../apb", features = ["unstructured", "orm", "activitypub-fe", "activitypub-counters", "litepub", "ostatus", "toot"] }
|
||||||
|
sea-orm = { version = "0.12", features = ["macros", "sqlx-sqlite", "runtime-tokio-rustls"] }
|
||||||
|
# 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" }
|
||||||
|
# mastodon
|
||||||
|
mastodon-async-entities = { version = "1.1.0", optional = true }
|
||||||
|
time = { version = "0.3", features = ["serde"], optional = true }
|
1
upub/routes/README.md
Normal file
1
upub/routes/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# upub routes
|
|
@ -1,20 +1,22 @@
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Path, Query, State};
|
||||||
use sea_orm::{ColumnTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, QueryFilter};
|
||||||
use crate::{errors::UpubError, model::{self, addressing::Event, attachment::BatchFillable}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}};
|
use upub::{model::{self, addressing::Event, attachment::BatchFillable}, server::{auth::AuthIdentity, fetcher::Fetcher, jsonld::LD}, Context};
|
||||||
|
|
||||||
use super::{jsonld::LD, JsonLD, TryFetch};
|
use crate::builders::JsonLD;
|
||||||
|
|
||||||
|
use super::TryFetch;
|
||||||
|
|
||||||
pub async fn view(
|
pub async fn view(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Query(query): Query<TryFetch>,
|
Query(query): Query<TryFetch>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let aid = ctx.aid(&id);
|
let aid = ctx.aid(&id);
|
||||||
if auth.is_local() && query.fetch && !ctx.is_local(&aid) {
|
if auth.is_local() && query.fetch && !ctx.is_local(&aid) {
|
||||||
let obj = ctx.fetch_activity(&aid).await?;
|
let obj = ctx.fetch_activity(&aid).await?;
|
||||||
if obj.id != aid {
|
if obj.id != aid {
|
||||||
return Err(UpubError::Redirect(obj.id));
|
return Err(upub::Error::Redirect(obj.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +26,7 @@ pub async fn view(
|
||||||
.into_model::<Event>()
|
.into_model::<Event>()
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(UpubError::not_found)?;
|
.ok_or_else(upub::Error::not_found)?;
|
||||||
|
|
||||||
let mut attachments = row.load_attachments_batch(ctx.db()).await?;
|
let mut attachments = row.load_attachments_batch(ctx.db()).await?;
|
||||||
let attach = attachments.remove(&row.internal());
|
let attach = attachments.remove(&row.internal());
|
|
@ -1,16 +1,15 @@
|
||||||
use apb::{ActorMut, BaseMut, ObjectMut, PublicKeyMut};
|
use apb::{ActorMut, BaseMut, ObjectMut, PublicKeyMut};
|
||||||
use axum::{extract::{Query, State}, http::HeaderMap, response::{IntoResponse, Redirect, Response}, Form, Json};
|
use axum::{extract::{Query, State}, http::HeaderMap, response::{IntoResponse, Redirect, Response}, Form, Json};
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
|
use upub::{server::{auth::AuthIdentity, fetcher::Fetcher, jsonld::LD}, Context};
|
||||||
|
|
||||||
use crate::{errors::UpubError, server::{auth::AuthIdentity, fetcher::Fetcher, Context}, url};
|
use crate::builders::JsonLD;
|
||||||
|
|
||||||
use super::{jsonld::LD, JsonLD};
|
|
||||||
|
|
||||||
|
|
||||||
pub async fn view(
|
pub async fn view(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
) -> crate::Result<Response> {
|
) -> upub::Result<Response> {
|
||||||
if let Some(accept) = headers.get("Accept") {
|
if let Some(accept) = headers.get("Accept") {
|
||||||
if let Ok(accept) = accept.to_str() {
|
if let Ok(accept) = accept.to_str() {
|
||||||
if accept.contains("text/html") && !accept.contains("application/ld+json") {
|
if accept.contains("text/html") && !accept.contains("application/ld+json") {
|
||||||
|
@ -19,20 +18,20 @@ pub async fn view(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(JsonLD(
|
Ok(JsonLD(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_id(Some(&url!(ctx, "")))
|
.set_id(Some(&upub::url!(ctx, "")))
|
||||||
.set_actor_type(Some(apb::ActorType::Application))
|
.set_actor_type(Some(apb::ActorType::Application))
|
||||||
.set_name(Some(&ctx.cfg().instance.name))
|
.set_name(Some(&ctx.cfg().instance.name))
|
||||||
.set_summary(Some(&ctx.cfg().instance.description))
|
.set_summary(Some(&ctx.cfg().instance.description))
|
||||||
.set_inbox(apb::Node::link(url!(ctx, "/inbox")))
|
.set_inbox(apb::Node::link(upub::url!(ctx, "/inbox")))
|
||||||
.set_outbox(apb::Node::link(url!(ctx, "/outbox")))
|
.set_outbox(apb::Node::link(upub::url!(ctx, "/outbox")))
|
||||||
.set_published(Some(ctx.actor().published))
|
.set_published(Some(ctx.actor().published))
|
||||||
.set_endpoints(apb::Node::Empty)
|
.set_endpoints(apb::Node::Empty)
|
||||||
.set_preferred_username(Some(ctx.domain()))
|
.set_preferred_username(Some(ctx.domain()))
|
||||||
.set_public_key(apb::Node::object(
|
.set_public_key(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_id(Some(&url!(ctx, "#main-key")))
|
.set_id(Some(&upub::url!(ctx, "#main-key")))
|
||||||
.set_owner(Some(&url!(ctx, "")))
|
.set_owner(Some(&upub::url!(ctx, "")))
|
||||||
.set_public_key_pem(&ctx.actor().public_key)
|
.set_public_key_pem(&ctx.actor().public_key)
|
||||||
))
|
))
|
||||||
.ld_context()
|
.ld_context()
|
||||||
|
@ -48,10 +47,10 @@ pub async fn proxy_get(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Query(query): Query<FetchPath>,
|
Query(query): Query<FetchPath>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<Json<serde_json::Value>> {
|
) -> upub::Result<Json<serde_json::Value>> {
|
||||||
// only local users can request fetches
|
// only local users can request fetches
|
||||||
if !ctx.cfg().security.allow_public_debugger && !auth.is_local() {
|
if !ctx.cfg().security.allow_public_debugger && !auth.is_local() {
|
||||||
return Err(UpubError::unauthorized());
|
return Err(upub::Error::unauthorized());
|
||||||
}
|
}
|
||||||
Ok(Json(
|
Ok(Json(
|
||||||
Context::request(
|
Context::request(
|
||||||
|
@ -72,10 +71,10 @@ pub async fn proxy_form(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Form(query): Form<FetchPath>,
|
Form(query): Form<FetchPath>,
|
||||||
) -> crate::Result<Json<serde_json::Value>> {
|
) -> upub::Result<Json<serde_json::Value>> {
|
||||||
// only local users can request fetches
|
// only local users can request fetches
|
||||||
if !ctx.cfg().security.allow_public_debugger && auth.is_local() {
|
if !ctx.cfg().security.allow_public_debugger && auth.is_local() {
|
||||||
return Err(UpubError::unauthorized());
|
return Err(upub::Error::unauthorized());
|
||||||
}
|
}
|
||||||
Ok(Json(
|
Ok(Json(
|
||||||
Context::request(
|
Context::request(
|
|
@ -1,8 +1,7 @@
|
||||||
use axum::{http::StatusCode, extract::State, Json};
|
use axum::{http::StatusCode, extract::State, Json};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use sea_orm::{ActiveValue::{Set, NotSet}, ColumnTrait, Condition, EntityTrait, QueryFilter};
|
use sea_orm::{ActiveValue::{Set, NotSet}, ColumnTrait, Condition, EntityTrait, QueryFilter};
|
||||||
|
use upub::{server::admin::Administrable, Context};
|
||||||
use crate::{errors::UpubError, model, server::{admin::Administrable, Context}};
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Deserialize)]
|
||||||
|
@ -30,12 +29,12 @@ fn token() -> String {
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Json(login): Json<LoginForm>
|
Json(login): Json<LoginForm>
|
||||||
) -> crate::Result<Json<AuthSuccess>> {
|
) -> upub::Result<Json<AuthSuccess>> {
|
||||||
// TODO salt the pwd
|
// TODO salt the pwd
|
||||||
match model::credential::Entity::find()
|
match upub::model::credential::Entity::find()
|
||||||
.filter(Condition::all()
|
.filter(Condition::all()
|
||||||
.add(model::credential::Column::Login.eq(login.email))
|
.add(upub::model::credential::Column::Login.eq(login.email))
|
||||||
.add(model::credential::Column::Password.eq(sha256::digest(login.password)))
|
.add(upub::model::credential::Column::Password.eq(sha256::digest(login.password)))
|
||||||
)
|
)
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await?
|
.await?
|
||||||
|
@ -43,8 +42,8 @@ pub async fn login(
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let token = token();
|
let token = token();
|
||||||
let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6);
|
let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6);
|
||||||
model::session::Entity::insert(
|
upub::model::session::Entity::insert(
|
||||||
model::session::ActiveModel {
|
upub::model::session::ActiveModel {
|
||||||
internal: sea_orm::ActiveValue::NotSet,
|
internal: sea_orm::ActiveValue::NotSet,
|
||||||
secret: sea_orm::ActiveValue::Set(token.clone()),
|
secret: sea_orm::ActiveValue::Set(token.clone()),
|
||||||
actor: sea_orm::ActiveValue::Set(x.actor.clone()),
|
actor: sea_orm::ActiveValue::Set(x.actor.clone()),
|
||||||
|
@ -58,7 +57,7 @@ pub async fn login(
|
||||||
user: x.actor
|
user: x.actor
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
None => Err(UpubError::unauthorized()),
|
None => Err(upub::Error::unauthorized()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,16 +69,16 @@ pub struct RefreshForm {
|
||||||
pub async fn refresh(
|
pub async fn refresh(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Json(login): Json<RefreshForm>
|
Json(login): Json<RefreshForm>
|
||||||
) -> crate::Result<Json<AuthSuccess>> {
|
) -> upub::Result<Json<AuthSuccess>> {
|
||||||
if !ctx.cfg().security.allow_login_refresh {
|
if !ctx.cfg().security.allow_login_refresh {
|
||||||
return Err(UpubError::forbidden());
|
return Err(upub::Error::forbidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev = model::session::Entity::find()
|
let prev = upub::model::session::Entity::find()
|
||||||
.filter(model::session::Column::Secret.eq(login.token))
|
.filter(upub::model::session::Column::Secret.eq(login.token))
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(UpubError::unauthorized)?;
|
.ok_or_else(upub::Error::unauthorized)?;
|
||||||
|
|
||||||
if prev.expires > chrono::Utc::now() {
|
if prev.expires > chrono::Utc::now() {
|
||||||
return Ok(Json(AuthSuccess { token: prev.secret, user: prev.actor, expires: prev.expires }));
|
return Ok(Json(AuthSuccess { token: prev.secret, user: prev.actor, expires: prev.expires }));
|
||||||
|
@ -88,13 +87,13 @@ pub async fn refresh(
|
||||||
let token = token();
|
let token = token();
|
||||||
let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6);
|
let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6);
|
||||||
let user = prev.actor;
|
let user = prev.actor;
|
||||||
let new_session = model::session::ActiveModel {
|
let new_session = upub::model::session::ActiveModel {
|
||||||
internal: NotSet,
|
internal: NotSet,
|
||||||
actor: Set(user.clone()),
|
actor: Set(user.clone()),
|
||||||
secret: Set(token.clone()),
|
secret: Set(token.clone()),
|
||||||
expires: Set(expires),
|
expires: Set(expires),
|
||||||
};
|
};
|
||||||
model::session::Entity::insert(new_session)
|
upub::model::session::Entity::insert(new_session)
|
||||||
.exec(ctx.db())
|
.exec(ctx.db())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -114,9 +113,9 @@ pub struct RegisterForm {
|
||||||
pub async fn register(
|
pub async fn register(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Json(registration): Json<RegisterForm>
|
Json(registration): Json<RegisterForm>
|
||||||
) -> crate::Result<Json<String>> {
|
) -> upub::Result<Json<String>> {
|
||||||
if !ctx.cfg().security.allow_registration {
|
if !ctx.cfg().security.allow_registration {
|
||||||
return Err(UpubError::forbidden());
|
return Err(upub::Error::forbidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.register_user(
|
ctx.register_user(
|
|
@ -1,14 +1,17 @@
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Path, Query, State};
|
||||||
use sea_orm::{ColumnTrait, Condition, PaginatorTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, Condition, PaginatorTrait, QueryFilter};
|
||||||
|
use upub::{model, server::auth::AuthIdentity, Context};
|
||||||
|
|
||||||
use crate::{model, routes::activitypub::{JsonLD, Pagination}, server::{auth::AuthIdentity, Context}, url};
|
use crate::builders::JsonLD;
|
||||||
|
|
||||||
|
use super::Pagination;
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let local_context_id = url!(ctx, "/context/{id}");
|
let local_context_id = upub::url!(ctx, "/context/{id}");
|
||||||
let context = ctx.oid(&id);
|
let context = ctx.oid(&id);
|
||||||
|
|
||||||
let count = model::addressing::Entity::find_addressed(auth.my_id())
|
let count = model::addressing::Entity::find_addressed(auth.my_id())
|
||||||
|
@ -17,7 +20,7 @@ pub async fn get(
|
||||||
.count(ctx.db())
|
.count(ctx.db())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
crate::server::builders::collection(&local_context_id, Some(count))
|
crate::builders::collection(&local_context_id, Some(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn page(
|
pub async fn page(
|
||||||
|
@ -25,11 +28,11 @@ pub async fn page(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Query(page): Query<Pagination>,
|
Query(page): Query<Pagination>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let context = ctx.oid(&id);
|
let context = ctx.oid(&id);
|
||||||
|
|
||||||
crate::server::builders::paginate(
|
crate::builders::paginate(
|
||||||
url!(ctx, "/context/{id}/page"),
|
upub::url!(ctx, "/context/{id}/page"),
|
||||||
Condition::all()
|
Condition::all()
|
||||||
.add(auth.filter_condition())
|
.add(auth.filter_condition())
|
||||||
.add(model::object::Column::Context.eq(context)),
|
.add(model::object::Column::Context.eq(context)),
|
|
@ -1,26 +1,27 @@
|
||||||
use apb::{server::Inbox, Activity, ActivityType};
|
use apb::{server::Inbox, Activity, ActivityType};
|
||||||
use axum::{extract::{Query, State}, http::StatusCode, Json};
|
use axum::{extract::{Query, State}, http::StatusCode, Json};
|
||||||
use sea_orm::{sea_query::IntoCondition, ColumnTrait};
|
use sea_orm::{sea_query::IntoCondition, ColumnTrait};
|
||||||
|
use upub::{server::auth::{AuthIdentity, Identity}, Context};
|
||||||
|
|
||||||
use crate::{errors::UpubError, server::{auth::{AuthIdentity, Identity}, Context}, url};
|
use crate::builders::JsonLD;
|
||||||
|
|
||||||
use super::{JsonLD, Pagination};
|
use super::Pagination;
|
||||||
|
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
crate::server::builders::collection(&url!(ctx, "/inbox"), None)
|
crate::builders::collection(&upub::url!(ctx, "/inbox"), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn page(
|
pub async fn page(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Query(page): Query<Pagination>,
|
Query(page): Query<Pagination>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
crate::server::builders::paginate(
|
crate::builders::paginate(
|
||||||
url!(ctx, "/inbox/page"),
|
upub::url!(ctx, "/inbox/page"),
|
||||||
crate::model::addressing::Column::Actor.is_null()
|
upub::model::addressing::Column::Actor.is_null()
|
||||||
.into_condition(),
|
.into_condition(),
|
||||||
ctx.db(),
|
ctx.db(),
|
||||||
page,
|
page,
|
||||||
|
@ -41,7 +42,7 @@ pub async fn post(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Json(activity): Json<serde_json::Value>
|
Json(activity): Json<serde_json::Value>
|
||||||
) -> crate::Result<()> {
|
) -> upub::Result<()> {
|
||||||
let Identity::Remote { domain: server, .. } = auth else {
|
let Identity::Remote { domain: server, .. } = auth else {
|
||||||
if activity.activity_type() == Some(ActivityType::Delete) {
|
if activity.activity_type() == Some(ActivityType::Delete) {
|
||||||
// this is spammy af, ignore them!
|
// this is spammy af, ignore them!
|
||||||
|
@ -54,24 +55,24 @@ pub async fn post(
|
||||||
}
|
}
|
||||||
tracing::warn!("refusing unauthorized activity: {}", pretty_json!(activity));
|
tracing::warn!("refusing unauthorized activity: {}", pretty_json!(activity));
|
||||||
if matches!(auth, Identity::Anonymous) {
|
if matches!(auth, Identity::Anonymous) {
|
||||||
return Err(UpubError::unauthorized());
|
return Err(upub::Error::unauthorized());
|
||||||
} else {
|
} else {
|
||||||
return Err(UpubError::forbidden());
|
return Err(upub::Error::forbidden());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(actor) = activity.actor().id() else {
|
let Some(actor) = activity.actor().id() else {
|
||||||
return Err(UpubError::bad_request());
|
return Err(upub::Error::bad_request());
|
||||||
};
|
};
|
||||||
|
|
||||||
if server != Context::server(&actor) {
|
if server != Context::server(&actor) {
|
||||||
return Err(UpubError::unauthorized());
|
return Err(upub::Error::unauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("processing federated activity: '{}'", serde_json::to_string(&activity).unwrap_or_default());
|
tracing::debug!("processing federated activity: '{}'", serde_json::to_string(&activity).unwrap_or_default());
|
||||||
|
|
||||||
// TODO we could process Links and bare Objects maybe, but probably out of AP spec?
|
// TODO we could process Links and bare Objects maybe, but probably out of AP spec?
|
||||||
match activity.activity_type().ok_or_else(UpubError::bad_request)? {
|
match activity.activity_type().ok_or_else(upub::Error::bad_request)? {
|
||||||
ActivityType::Activity => {
|
ActivityType::Activity => {
|
||||||
tracing::warn!("skipping unprocessable base activity: {}", pretty_json!(activity));
|
tracing::warn!("skipping unprocessable base activity: {}", pretty_json!(activity));
|
||||||
Err(StatusCode::UNPROCESSABLE_ENTITY.into()) // won't ingest useless stuff
|
Err(StatusCode::UNPROCESSABLE_ENTITY.into()) // won't ingest useless stuff
|
|
@ -8,18 +8,15 @@ pub mod application;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod well_known;
|
pub mod well_known;
|
||||||
|
|
||||||
pub mod jsonld;
|
|
||||||
pub use jsonld::JsonLD;
|
|
||||||
|
|
||||||
use axum::{http::StatusCode, response::IntoResponse, routing::{get, patch, post, put}, Router};
|
use axum::{http::StatusCode, response::IntoResponse, routing::{get, patch, post, put}, Router};
|
||||||
|
|
||||||
pub trait ActivityPubRouter {
|
pub trait ActivityPubRouter {
|
||||||
fn ap_routes(self) -> Self;
|
fn ap_routes(self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActivityPubRouter for Router<crate::server::Context> {
|
impl ActivityPubRouter for Router<upub::Context> {
|
||||||
fn ap_routes(self) -> Self {
|
fn ap_routes(self) -> Self {
|
||||||
use crate::routes::activitypub as ap; // TODO use self ?
|
use crate::activitypub as ap; // TODO use self ?
|
||||||
|
|
||||||
self
|
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?
|
|
@ -3,23 +3,24 @@ pub mod replies;
|
||||||
use apb::{CollectionMut, ObjectMut};
|
use apb::{CollectionMut, ObjectMut};
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Path, Query, State};
|
||||||
use sea_orm::{ColumnTrait, ModelTrait, QueryFilter, QuerySelect, SelectColumns};
|
use sea_orm::{ColumnTrait, ModelTrait, QueryFilter, QuerySelect, SelectColumns};
|
||||||
|
use upub::{model::{self, addressing::Event}, server::{auth::AuthIdentity, fetcher::Fetcher, jsonld::LD}, Context};
|
||||||
|
|
||||||
use crate::{errors::UpubError, model::{self, addressing::Event}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}};
|
use crate::builders::JsonLD;
|
||||||
|
|
||||||
use super::{jsonld::LD, JsonLD, TryFetch};
|
use super::TryFetch;
|
||||||
|
|
||||||
pub async fn view(
|
pub async fn view(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Query(query): Query<TryFetch>,
|
Query(query): Query<TryFetch>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let oid = ctx.oid(&id);
|
let oid = ctx.oid(&id);
|
||||||
if auth.is_local() && query.fetch && !ctx.is_local(&oid) {
|
if auth.is_local() && query.fetch && !ctx.is_local(&oid) {
|
||||||
let obj = ctx.fetch_object(&oid).await?;
|
let obj = ctx.fetch_object(&oid).await?;
|
||||||
// some implementations serve statuses on different urls than their AP id
|
// some implementations serve statuses on different urls than their AP id
|
||||||
if obj.id != oid {
|
if obj.id != oid {
|
||||||
return Err(UpubError::Redirect(crate::url!(ctx, "/objects/{}", ctx.id(&obj.id))));
|
return Err(upub::Error::Redirect(upub::url!(ctx, "/objects/{}", ctx.id(&obj.id))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,11 +30,11 @@ pub async fn view(
|
||||||
.into_model::<Event>()
|
.into_model::<Event>()
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(UpubError::not_found)?;
|
.ok_or_else(upub::Error::not_found)?;
|
||||||
|
|
||||||
let object = match item {
|
let object = match item {
|
||||||
Event::Tombstone => return Err(UpubError::not_found()),
|
Event::Tombstone => return Err(upub::Error::not_found()),
|
||||||
Event::Activity(_) => return Err(UpubError::not_found()),
|
Event::Activity(_) => return Err(upub::Error::not_found()),
|
||||||
Event::StrayObject { liked: _, object } => object,
|
Event::StrayObject { liked: _, object } => object,
|
||||||
Event::DeepActivity { activity: _, liked: _, object } => object,
|
Event::DeepActivity { activity: _, liked: _, object } => object,
|
||||||
};
|
};
|
||||||
|
@ -58,9 +59,9 @@ pub async fn view(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
replies = apb::Node::object(
|
replies = apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
// .set_id(Some(&crate::url!(ctx, "/objects/{id}/replies")))
|
// .set_id(Some(&upub::url!(ctx, "/objects/{id}/replies")))
|
||||||
// .set_first(apb::Node::link(crate::url!(ctx, "/objects/{id}/replies/page")))
|
// .set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page")))
|
||||||
.set_collection_type(Some(apb::CollectionType::Collection))
|
.set_collection_type(Some(apb::CollectionType::Collection))
|
||||||
.set_total_items(Some(object.replies as u64))
|
.set_total_items(Some(object.replies as u64))
|
||||||
.set_items(apb::Node::links(replies_ids))
|
.set_items(apb::Node::links(replies_ids))
|
|
@ -1,15 +1,16 @@
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Path, Query, State};
|
||||||
use sea_orm::{ColumnTrait, Condition, PaginatorTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, Condition, PaginatorTrait, QueryFilter};
|
||||||
|
use upub::{model, server::{auth::AuthIdentity, fetcher::Fetcher}, Context};
|
||||||
|
|
||||||
use crate::{model, routes::activitypub::{JsonLD, Pagination, TryFetch}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}, url};
|
use crate::{activitypub::{Pagination, TryFetch}, builders::JsonLD};
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Query(q): Query<TryFetch>,
|
Query(q): Query<TryFetch>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let replies_id = url!(ctx, "/objects/{id}/replies");
|
let replies_id = upub::url!(ctx, "/objects/{id}/replies");
|
||||||
let oid = ctx.oid(&id);
|
let oid = ctx.oid(&id);
|
||||||
|
|
||||||
if auth.is_local() && q.fetch {
|
if auth.is_local() && q.fetch {
|
||||||
|
@ -22,7 +23,7 @@ pub async fn get(
|
||||||
.count(ctx.db())
|
.count(ctx.db())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
crate::server::builders::collection(&replies_id, Some(count))
|
crate::builders::collection(&replies_id, Some(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn page(
|
pub async fn page(
|
||||||
|
@ -30,11 +31,11 @@ pub async fn page(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Query(page): Query<Pagination>,
|
Query(page): Query<Pagination>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let page_id = url!(ctx, "/objects/{id}/replies/page");
|
let page_id = upub::url!(ctx, "/objects/{id}/replies/page");
|
||||||
let oid = ctx.oid(&id);
|
let oid = ctx.oid(&id);
|
||||||
|
|
||||||
crate::server::builders::paginate(
|
crate::builders::paginate(
|
||||||
page_id,
|
page_id,
|
||||||
Condition::all()
|
Condition::all()
|
||||||
.add(auth.filter_condition())
|
.add(auth.filter_condition())
|
|
@ -1,22 +1,23 @@
|
||||||
use axum::{extract::{Query, State}, http::StatusCode, Json};
|
use axum::{extract::{Query, State}, http::StatusCode, Json};
|
||||||
use sea_orm::{ColumnTrait, Condition};
|
use sea_orm::{ColumnTrait, Condition};
|
||||||
|
use upub::{server::auth::AuthIdentity, Context};
|
||||||
|
|
||||||
use crate::{errors::UpubError, routes::activitypub::{CreationResult, JsonLD, Pagination}, server::{auth::AuthIdentity, Context}, url};
|
use crate::{activitypub::{CreationResult, Pagination}, builders::JsonLD};
|
||||||
|
|
||||||
pub async fn get(State(ctx): State<Context>) -> crate::Result<JsonLD<serde_json::Value>> {
|
pub async fn get(State(ctx): State<Context>) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
crate::server::builders::collection(&url!(ctx, "/outbox"), None)
|
crate::builders::collection(&upub::url!(ctx, "/outbox"), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn page(
|
pub async fn page(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Query(page): Query<Pagination>,
|
Query(page): Query<Pagination>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
crate::server::builders::paginate(
|
crate::builders::paginate(
|
||||||
url!(ctx, "/outbox/page"),
|
upub::url!(ctx, "/outbox/page"),
|
||||||
Condition::all()
|
Condition::all()
|
||||||
.add(auth.filter_condition())
|
.add(auth.filter_condition())
|
||||||
.add(crate::model::actor::Column::Domain.eq(ctx.domain().to_string())),
|
.add(upub::model::actor::Column::Domain.eq(ctx.domain().to_string())),
|
||||||
ctx.db(),
|
ctx.db(),
|
||||||
page,
|
page,
|
||||||
auth.my_id(),
|
auth.my_id(),
|
||||||
|
@ -29,7 +30,7 @@ pub async fn post(
|
||||||
State(_ctx): State<Context>,
|
State(_ctx): State<Context>,
|
||||||
AuthIdentity(_auth): AuthIdentity,
|
AuthIdentity(_auth): AuthIdentity,
|
||||||
Json(_activity): Json<serde_json::Value>,
|
Json(_activity): Json<serde_json::Value>,
|
||||||
) -> Result<CreationResult, UpubError> {
|
) -> upub::Result<CreationResult> {
|
||||||
// TODO administrative actions may be carried out against this outbox?
|
// TODO administrative actions may be carried out against this outbox?
|
||||||
Err(StatusCode::NOT_IMPLEMENTED.into())
|
Err(StatusCode::NOT_IMPLEMENTED.into())
|
||||||
}
|
}
|
|
@ -1,15 +1,16 @@
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Path, Query, State};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QuerySelect, SelectColumns};
|
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::{routes::activitypub::{JsonLD, Pagination}, model, server::Context, url};
|
use upub::{model, Context};
|
||||||
|
|
||||||
use model::relation::Column::{Following, Follower};
|
use crate::{activitypub::Pagination, builders::JsonLD};
|
||||||
|
|
||||||
pub async fn get<const OUTGOING: bool>(
|
pub async fn get<const OUTGOING: bool>(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let follow___ = if OUTGOING { "following" } else { "followers" };
|
let follow___ = if OUTGOING { "following" } else { "followers" };
|
||||||
|
use upub::model::relation::Column::{Follower, Following};
|
||||||
let count = model::relation::Entity::find()
|
let count = model::relation::Entity::find()
|
||||||
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(&id)))
|
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(&id)))
|
||||||
.count(ctx.db()).await.unwrap_or_else(|e| {
|
.count(ctx.db()).await.unwrap_or_else(|e| {
|
||||||
|
@ -17,18 +18,19 @@ pub async fn get<const OUTGOING: bool>(
|
||||||
0
|
0
|
||||||
});
|
});
|
||||||
|
|
||||||
crate::server::builders::collection(&url!(ctx, "/actors/{id}/{follow___}"), Some(count))
|
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/{follow___}"), Some(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn page<const OUTGOING: bool>(
|
pub async fn page<const OUTGOING: bool>(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Query(page): Query<Pagination>,
|
Query(page): Query<Pagination>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let follow___ = if OUTGOING { "following" } else { "followers" };
|
let follow___ = if OUTGOING { "following" } else { "followers" };
|
||||||
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);
|
||||||
|
|
||||||
|
use upub::model::relation::Column::{Follower, Following};
|
||||||
let following = model::relation::Entity::find()
|
let following = model::relation::Entity::find()
|
||||||
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(&id)))
|
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(&id)))
|
||||||
.select_only()
|
.select_only()
|
||||||
|
@ -39,8 +41,8 @@ pub async fn page<const OUTGOING: bool>(
|
||||||
.all(ctx.db())
|
.all(ctx.db())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
crate::server::builders::collection_page(
|
crate::builders::collection_page(
|
||||||
&url!(ctx, "/actors/{id}/{follow___}/page"),
|
&upub::url!(ctx, "/actors/{id}/{follow___}/page"),
|
||||||
offset, limit,
|
offset, limit,
|
||||||
following.into_iter().map(serde_json::Value::String).collect()
|
following.into_iter().map(serde_json::Value::String).collect()
|
||||||
)
|
)
|
|
@ -1,18 +1,20 @@
|
||||||
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
||||||
|
|
||||||
use sea_orm::{ColumnTrait, Condition};
|
use sea_orm::{ColumnTrait, Condition};
|
||||||
use crate::{errors::UpubError, model, routes::activitypub::{JsonLD, Pagination}, server::{auth::{AuthIdentity, Identity}, Context}, url};
|
use upub::{model, server::auth::{AuthIdentity, Identity}, Context};
|
||||||
|
|
||||||
|
use crate::{activitypub::Pagination, builders::JsonLD};
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
match auth {
|
match auth {
|
||||||
Identity::Anonymous => Err(StatusCode::FORBIDDEN.into()),
|
Identity::Anonymous => Err(StatusCode::FORBIDDEN.into()),
|
||||||
Identity::Remote { .. } => Err(StatusCode::FORBIDDEN.into()),
|
Identity::Remote { .. } => Err(StatusCode::FORBIDDEN.into()),
|
||||||
Identity::Local { id: user, .. } => if ctx.uid(&id) == user {
|
Identity::Local { id: user, .. } => if ctx.uid(&id) == user {
|
||||||
crate::server::builders::collection(&url!(ctx, "/actors/{id}/inbox"), None)
|
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/inbox"), None)
|
||||||
} else {
|
} else {
|
||||||
Err(StatusCode::FORBIDDEN.into())
|
Err(StatusCode::FORBIDDEN.into())
|
||||||
},
|
},
|
||||||
|
@ -24,17 +26,17 @@ pub async fn page(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Query(page): Query<Pagination>,
|
Query(page): Query<Pagination>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let Identity::Local { id: uid, internal } = &auth else {
|
let Identity::Local { id: uid, internal } = &auth else {
|
||||||
// local inbox is only for local users
|
// local inbox is only for local users
|
||||||
return Err(UpubError::forbidden());
|
return Err(upub::Error::forbidden());
|
||||||
};
|
};
|
||||||
if uid != &ctx.uid(&id) {
|
if uid != &ctx.uid(&id) {
|
||||||
return Err(UpubError::forbidden());
|
return Err(upub::Error::forbidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::server::builders::paginate(
|
crate::builders::paginate(
|
||||||
url!(ctx, "/actors/{id}/inbox/page"),
|
upub::url!(ctx, "/actors/{id}/inbox/page"),
|
||||||
Condition::any()
|
Condition::any()
|
||||||
.add(model::addressing::Column::Actor.eq(*internal))
|
.add(model::addressing::Column::Actor.eq(*internal))
|
||||||
.add(model::object::Column::AttributedTo.eq(uid))
|
.add(model::object::Column::AttributedTo.eq(uid))
|
||||||
|
@ -52,7 +54,7 @@ pub async fn post(
|
||||||
Path(_id): Path<String>,
|
Path(_id): Path<String>,
|
||||||
AuthIdentity(_auth): AuthIdentity,
|
AuthIdentity(_auth): AuthIdentity,
|
||||||
Json(activity): Json<serde_json::Value>,
|
Json(activity): Json<serde_json::Value>,
|
||||||
) -> Result<(), UpubError> {
|
) -> Result<(), upub::Error> {
|
||||||
// POSTing to user inboxes is effectively the same as POSTing to the main inbox
|
// POSTing to user inboxes is effectively the same as POSTing to the main inbox
|
||||||
super::super::inbox::post(State(ctx), AuthIdentity(_auth), Json(activity)).await
|
super::super::inbox::post(State(ctx), AuthIdentity(_auth), Json(activity)).await
|
||||||
}
|
}
|
|
@ -7,9 +7,11 @@ pub mod following;
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Path, Query, State};
|
||||||
|
|
||||||
use apb::{ActorMut, EndpointsMut, Node, ObjectMut};
|
use apb::{ActorMut, EndpointsMut, Node, ObjectMut};
|
||||||
use crate::{errors::UpubError, model, server::{auth::AuthIdentity, builders::AnyQuery, fetcher::Fetcher, Context}, url};
|
use upub::{ext::AnyQuery, model, server::{auth::AuthIdentity, fetcher::Fetcher, jsonld::LD}, Context};
|
||||||
|
|
||||||
use super::{jsonld::LD, JsonLD, TryFetch};
|
use crate::builders::JsonLD;
|
||||||
|
|
||||||
|
use super::TryFetch;
|
||||||
|
|
||||||
|
|
||||||
pub async fn view(
|
pub async fn view(
|
||||||
|
@ -17,7 +19,7 @@ pub async fn view(
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Query(query): Query<TryFetch>,
|
Query(query): Query<TryFetch>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let mut uid = ctx.uid(&id);
|
let mut uid = ctx.uid(&id);
|
||||||
if auth.is_local() {
|
if auth.is_local() {
|
||||||
if id.starts_with('@') {
|
if id.starts_with('@') {
|
||||||
|
@ -50,16 +52,16 @@ pub async fn view(
|
||||||
// local user
|
// local user
|
||||||
Some((user_model, Some(cfg))) => {
|
Some((user_model, Some(cfg))) => {
|
||||||
let mut user = user_model.ap()
|
let mut user = user_model.ap()
|
||||||
.set_inbox(Node::link(url!(ctx, "/actors/{id}/inbox")))
|
.set_inbox(Node::link(upub::url!(ctx, "/actors/{id}/inbox")))
|
||||||
.set_outbox(Node::link(url!(ctx, "/actors/{id}/outbox")))
|
.set_outbox(Node::link(upub::url!(ctx, "/actors/{id}/outbox")))
|
||||||
.set_following(Node::link(url!(ctx, "/actors/{id}/following")))
|
.set_following(Node::link(upub::url!(ctx, "/actors/{id}/following")))
|
||||||
.set_followers(Node::link(url!(ctx, "/actors/{id}/followers")))
|
.set_followers(Node::link(upub::url!(ctx, "/actors/{id}/followers")))
|
||||||
.set_following_me(following_me)
|
.set_following_me(following_me)
|
||||||
.set_followed_by_me(followed_by_me)
|
.set_followed_by_me(followed_by_me)
|
||||||
.set_endpoints(Node::object(
|
.set_endpoints(Node::object(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_shared_inbox(Some(&url!(ctx, "/inbox")))
|
.set_shared_inbox(Some(&upub::url!(ctx, "/inbox")))
|
||||||
.set_proxy_url(Some(&url!(ctx, "/proxy")))
|
.set_proxy_url(Some(&upub::url!(ctx, "/proxy")))
|
||||||
));
|
));
|
||||||
|
|
||||||
if !auth.is(&uid) && !cfg.show_followers_count {
|
if !auth.is(&uid) && !cfg.show_followers_count {
|
||||||
|
@ -83,7 +85,7 @@ pub async fn view(
|
||||||
.set_followed_by_me(followed_by_me)
|
.set_followed_by_me(followed_by_me)
|
||||||
.ld_context()
|
.ld_context()
|
||||||
)),
|
)),
|
||||||
None => Err(UpubError::not_found()),
|
None => Err(upub::Error::not_found()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,15 @@ use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
||||||
use sea_orm::{ColumnTrait, Condition};
|
use sea_orm::{ColumnTrait, Condition};
|
||||||
|
|
||||||
use apb::{server::Outbox, AcceptType, ActivityType, Base, BaseType, ObjectType, RejectType};
|
use apb::{server::Outbox, AcceptType, ActivityType, Base, BaseType, ObjectType, RejectType};
|
||||||
use crate::{errors::UpubError, model, routes::activitypub::{CreationResult, JsonLD, Pagination}, server::{auth::{AuthIdentity, Identity}, Context}, url};
|
use upub::{model, server::auth::{AuthIdentity, Identity}, Context};
|
||||||
|
|
||||||
|
use crate::{activitypub::{CreationResult, Pagination}, builders::JsonLD};
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
crate::server::builders::collection(&url!(ctx, "/actors/{id}/outbox"), None)
|
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/outbox"), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn page(
|
pub async fn page(
|
||||||
|
@ -16,10 +18,10 @@ pub async fn page(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Query(page): Query<Pagination>,
|
Query(page): Query<Pagination>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let uid = ctx.uid(&id);
|
let uid = ctx.uid(&id);
|
||||||
crate::server::builders::paginate(
|
crate::builders::paginate(
|
||||||
url!(ctx, "/actors/{id}/outbox/page"),
|
upub::url!(ctx, "/actors/{id}/outbox/page"),
|
||||||
Condition::all()
|
Condition::all()
|
||||||
.add(auth.filter_condition())
|
.add(auth.filter_condition())
|
||||||
.add(
|
.add(
|
||||||
|
@ -40,7 +42,7 @@ pub async fn post(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Json(activity): Json<serde_json::Value>,
|
Json(activity): Json<serde_json::Value>,
|
||||||
) -> Result<CreationResult, UpubError> {
|
) -> upub::Result<CreationResult> {
|
||||||
match auth {
|
match auth {
|
||||||
Identity::Anonymous => Err(StatusCode::UNAUTHORIZED.into()),
|
Identity::Anonymous => Err(StatusCode::UNAUTHORIZED.into()),
|
||||||
Identity::Remote { .. } => Err(StatusCode::NOT_IMPLEMENTED.into()),
|
Identity::Remote { .. } => Err(StatusCode::NOT_IMPLEMENTED.into()),
|
|
@ -1,8 +1,7 @@
|
||||||
use axum::{extract::{Path, Query, State}, http::StatusCode, response::{IntoResponse, Response}, Json};
|
use axum::{extract::{Path, Query, State}, http::StatusCode, response::{IntoResponse, Response}, Json};
|
||||||
use jrd::{JsonResourceDescriptor, JsonResourceDescriptorLink};
|
use jrd::{JsonResourceDescriptor, JsonResourceDescriptorLink};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter};
|
||||||
|
use upub::{model, Context};
|
||||||
use crate::{errors::UpubError, model, server::Context, url, VERSION};
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct NodeInfoDiscovery {
|
pub struct NodeInfoDiscovery {
|
||||||
|
@ -20,11 +19,11 @@ pub async fn nodeinfo_discovery(State(ctx): State<Context>) -> Json<NodeInfoDisc
|
||||||
links: vec![
|
links: vec![
|
||||||
NodeInfoDiscoveryRel {
|
NodeInfoDiscoveryRel {
|
||||||
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0".into(),
|
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0".into(),
|
||||||
href: crate::url!(ctx, "/nodeinfo/2.0.json"),
|
href: upub::url!(ctx, "/nodeinfo/2.0.json"),
|
||||||
},
|
},
|
||||||
NodeInfoDiscoveryRel {
|
NodeInfoDiscoveryRel {
|
||||||
rel: "http://nodeinfo.diaspora.software/ns/schema/2.1".into(),
|
rel: "http://nodeinfo.diaspora.software/ns/schema/2.1".into(),
|
||||||
href: crate::url!(ctx, "/nodeinfo/2.1.json"),
|
href: upub::url!(ctx, "/nodeinfo/2.1.json"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -42,7 +41,7 @@ pub async fn nodeinfo(State(ctx): State<Context>, Path(version): Path<String>) -
|
||||||
"2.0.json" | "2.0" => (
|
"2.0.json" | "2.0" => (
|
||||||
nodeinfo::types::Software {
|
nodeinfo::types::Software {
|
||||||
name: "μpub".to_string(),
|
name: "μpub".to_string(),
|
||||||
version: Some(VERSION.into()),
|
version: Some(upub::VERSION.into()),
|
||||||
repository: None,
|
repository: None,
|
||||||
homepage: None,
|
homepage: None,
|
||||||
},
|
},
|
||||||
|
@ -51,7 +50,7 @@ pub async fn nodeinfo(State(ctx): State<Context>, Path(version): Path<String>) -
|
||||||
"2.1.json" | "2.1" => (
|
"2.1.json" | "2.1" => (
|
||||||
nodeinfo::types::Software {
|
nodeinfo::types::Software {
|
||||||
name: "μpub".to_string(),
|
name: "μpub".to_string(),
|
||||||
version: Some(VERSION.into()),
|
version: Some(upub::VERSION.into()),
|
||||||
repository: Some("https://git.alemi.dev/upub.git/".into()),
|
repository: Some("https://git.alemi.dev/upub.git/".into()),
|
||||||
homepage: None,
|
homepage: None,
|
||||||
},
|
},
|
||||||
|
@ -96,7 +95,7 @@ impl<T: serde::Serialize> IntoResponse for JsonRD<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<WebfingerQuery>) -> crate::Result<JsonRD<JsonResourceDescriptor>> {
|
pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<WebfingerQuery>) -> upub::Result<JsonRD<JsonResourceDescriptor>> {
|
||||||
if let Some((user, domain)) = query
|
if let Some((user, domain)) = query
|
||||||
.resource
|
.resource
|
||||||
.replace("acct:", "")
|
.replace("acct:", "")
|
||||||
|
@ -107,7 +106,7 @@ pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<Webfinger
|
||||||
.filter(model::actor::Column::Domain.eq(domain))
|
.filter(model::actor::Column::Domain.eq(domain))
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(UpubError::not_found)?;
|
.ok_or_else(upub::Error::not_found)?;
|
||||||
|
|
||||||
let expires = if domain == ctx.domain() {
|
let expires = if domain == ctx.domain() {
|
||||||
// TODO configurable webfinger TTL, also 30 days may be too much???
|
// TODO configurable webfinger TTL, also 30 days may be too much???
|
||||||
|
@ -163,10 +162,10 @@ pub struct OauthAuthorizationServerResponse {
|
||||||
authorization_response_iss_parameter_supported: bool,
|
authorization_response_iss_parameter_supported: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn oauth_authorization_server(State(ctx): State<Context>) -> crate::Result<Json<OauthAuthorizationServerResponse>> {
|
pub async fn oauth_authorization_server(State(ctx): State<Context>) -> upub::Result<Json<OauthAuthorizationServerResponse>> {
|
||||||
Ok(Json(OauthAuthorizationServerResponse {
|
Ok(Json(OauthAuthorizationServerResponse {
|
||||||
issuer: url!(ctx, ""),
|
issuer: upub::url!(ctx, ""),
|
||||||
authorization_endpoint: url!(ctx, "/auth"),
|
authorization_endpoint: upub::url!(ctx, "/auth"),
|
||||||
token_endpoint: "".to_string(),
|
token_endpoint: "".to_string(),
|
||||||
scopes_supported: vec![
|
scopes_supported: vec![
|
||||||
"read:account".to_string(),
|
"read:account".to_string(),
|
|
@ -1,7 +1,9 @@
|
||||||
use apb::{BaseMut, CollectionMut, CollectionPageMut};
|
use apb::{BaseMut, CollectionMut, CollectionPageMut};
|
||||||
use sea_orm::{Condition, DatabaseConnection, QueryFilter, QuerySelect, RelationTrait};
|
use sea_orm::{Condition, DatabaseConnection, QueryFilter, QuerySelect, RelationTrait};
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
|
||||||
use crate::{model::{addressing::Event, attachment::BatchFillable}, routes::activitypub::{jsonld::LD, JsonLD, Pagination}};
|
use upub::{model::{addressing::Event, attachment::BatchFillable}, server::jsonld::LD};
|
||||||
|
use crate::activitypub::Pagination;
|
||||||
|
|
||||||
pub async fn paginate(
|
pub async fn paginate(
|
||||||
id: String,
|
id: String,
|
||||||
|
@ -10,15 +12,15 @@ pub async fn paginate(
|
||||||
page: Pagination,
|
page: Pagination,
|
||||||
my_id: Option<i64>,
|
my_id: Option<i64>,
|
||||||
with_users: bool, // TODO ewww too many arguments for this weird function...
|
with_users: bool, // TODO ewww too many arguments for this weird function...
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
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 select = crate::model::addressing::Entity::find_addressed(my_id);
|
let mut select = upub::model::addressing::Entity::find_addressed(my_id);
|
||||||
|
|
||||||
if with_users {
|
if with_users {
|
||||||
select = select
|
select = select
|
||||||
.join(sea_orm::JoinType::InnerJoin, crate::model::activity::Relation::Actors.def());
|
.join(sea_orm::JoinType::InnerJoin, upub::model::activity::Relation::Actors.def());
|
||||||
}
|
}
|
||||||
|
|
||||||
let items = select
|
let items = select
|
||||||
|
@ -43,14 +45,14 @@ pub async fn paginate(
|
||||||
collection_page(&id, offset, limit, items)
|
collection_page(&id, offset, limit, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collection_page(id: &str, offset: u64, limit: u64, items: Vec<serde_json::Value>) -> crate::Result<JsonLD<serde_json::Value>> {
|
pub fn collection_page(id: &str, offset: u64, limit: u64, items: Vec<serde_json::Value>) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
let next = if items.len() < limit as usize {
|
let next = if items.len() < limit as usize {
|
||||||
apb::Node::Empty
|
apb::Node::Empty
|
||||||
} else {
|
} else {
|
||||||
apb::Node::link(format!("{id}?offset={}", offset+limit))
|
apb::Node::link(format!("{id}?offset={}", offset+limit))
|
||||||
};
|
};
|
||||||
Ok(JsonLD(
|
Ok(JsonLD(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_id(Some(&format!("{id}?offset={offset}")))
|
.set_id(Some(&format!("{id}?offset={offset}")))
|
||||||
.set_collection_type(Some(apb::CollectionType::OrderedCollectionPage))
|
.set_collection_type(Some(apb::CollectionType::OrderedCollectionPage))
|
||||||
.set_part_of(apb::Node::link(id.replace("/page", "")))
|
.set_part_of(apb::Node::link(id.replace("/page", "")))
|
||||||
|
@ -61,9 +63,9 @@ pub fn collection_page(id: &str, offset: u64, limit: u64, items: Vec<serde_json:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn collection(id: &str, total_items: Option<u64>) -> crate::Result<JsonLD<serde_json::Value>> {
|
pub fn collection(id: &str, total_items: Option<u64>) -> upub::Result<JsonLD<serde_json::Value>> {
|
||||||
Ok(JsonLD(
|
Ok(JsonLD(
|
||||||
serde_json::Value::new_object()
|
apb::new()
|
||||||
.set_id(Some(id))
|
.set_id(Some(id))
|
||||||
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
||||||
.set_first(apb::Node::link(format!("{id}/page")))
|
.set_first(apb::Node::link(format!("{id}/page")))
|
||||||
|
@ -72,21 +74,14 @@ pub fn collection(id: &str, total_items: Option<u64>) -> crate::Result<JsonLD<se
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::async_trait]
|
// got this from https://github.com/kitsune-soc/kitsune/blob/b023a12b687dd9a274233a5a9950f2de5e192344/kitsune/src/http/responder.rs
|
||||||
pub trait AnyQuery {
|
// i was trying to do it with middlewares but this is way cleaner
|
||||||
async fn any(self, db: &sea_orm::DatabaseConnection) -> crate::Result<bool>;
|
pub struct JsonLD<T>(pub T);
|
||||||
}
|
impl<T: serde::Serialize> IntoResponse for JsonLD<T> {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
#[axum::async_trait]
|
(
|
||||||
impl<T : sea_orm::EntityTrait> AnyQuery for sea_orm::Select<T> {
|
[("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")],
|
||||||
async fn any(self, db: &sea_orm::DatabaseConnection) -> crate::Result<bool> {
|
axum::Json(self.0)
|
||||||
Ok(self.one(db).await?.is_some())
|
).into_response()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::async_trait]
|
|
||||||
impl<T : sea_orm::SelectorTrait + Send> AnyQuery for sea_orm::Selector<T> {
|
|
||||||
async fn any(self, db: &sea_orm::DatabaseConnection) -> crate::Result<bool> {
|
|
||||||
Ok(self.one(db).await?.is_some())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
39
upub/routes/src/lib.rs
Normal file
39
upub/routes/src/lib.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
pub mod activitypub;
|
||||||
|
|
||||||
|
#[cfg(feature = "web")]
|
||||||
|
pub mod web;
|
||||||
|
|
||||||
|
#[cfg(feature = "mastodon")]
|
||||||
|
pub mod mastodon;
|
||||||
|
|
||||||
|
pub mod builders;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "mastodon"))]
|
||||||
|
pub mod mastodon {
|
||||||
|
pub trait MastodonRouter {
|
||||||
|
fn mastodon_routes(self) -> Self where Self: Sized { self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MastodonRouter for axum::Router<upub::Context> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve(ctx: upub::Context, bind: String) -> upub::Result<()> {
|
||||||
|
use activitypub::ActivityPubRouter;
|
||||||
|
use mastodon::MastodonRouter;
|
||||||
|
use tower_http::{cors::CorsLayer, trace::TraceLayer};
|
||||||
|
|
||||||
|
let router = axum::Router::new()
|
||||||
|
.ap_routes()
|
||||||
|
.mastodon_routes() // no-op if mastodon feature is disabled
|
||||||
|
.layer(CorsLayer::permissive())
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
|
.with_state(ctx);
|
||||||
|
|
||||||
|
// run our app with hyper, listening locally on port 3000
|
||||||
|
let listener = tokio::net::TcpListener::bind(bind)
|
||||||
|
.await.expect("could not bind tcp socket");
|
||||||
|
|
||||||
|
axum::serve(listener, router).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -12,12 +12,10 @@ repository = "https://git.alemi.dev/upub.git"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4"
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
tracing-subscriber-wasm = "0.1"
|
tracing-subscriber-wasm = "0.1"
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
thiserror = "1"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_default = "0.1"
|
serde_default = "0.1"
|
||||||
|
@ -26,13 +24,13 @@ dashmap = "5.5"
|
||||||
leptos = { version = "0.6", features = ["csr", "tracing"] }
|
leptos = { version = "0.6", features = ["csr", "tracing"] }
|
||||||
leptos_router = { version = "0.6", features = ["csr"] }
|
leptos_router = { version = "0.6", features = ["csr"] }
|
||||||
leptos-use = { version = "0.10", features = ["serde"] }
|
leptos-use = { version = "0.10", features = ["serde"] }
|
||||||
|
web-sys = { version = "0.3", features = ["Screen"] }
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
apb = { path = "../apb", features = ["unstructured", "activitypub-fe", "activitypub-counters", "litepub"] }
|
apb = { path = "../apb", features = ["unstructured", "activitypub-fe", "activitypub-counters", "litepub"] }
|
||||||
uriproxy = { path = "../utils/uriproxy/" }
|
uriproxy = { path = "../utils/uriproxy/" }
|
||||||
|
mdhtml = { path = "../utils/mdhtml/" }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
web-sys = { version = "0.3", features = ["Screen"] }
|
|
||||||
mdhtml = { path = "../utils/mdhtml/" }
|
|
||||||
jrd = "0.1"
|
jrd = "0.1"
|
||||||
tld = "2.35"
|
tld = "2.35"
|
||||||
|
|
0
web/README.md
Normal file
0
web/README.md
Normal file
Loading…
Reference in a new issue