feat: use cargo-leptos for all-in-one ssr binary..

... that doesn't work?!? spent hours getting this to compile, it
munched 20GB like it was nothing, took its damn time just to then crash
while running because "cannot access imported statics on non-wasm
targets" ?!?!!?? no clue, also not super sold on this SSR thing because
it adds so much complexity, will probably leave this branch up here for
future reference in case i want to try this again, and go back to trunk
+ include! static assets and full CSR for leptos
This commit is contained in:
əlemi 2025-01-21 02:39:43 +01:00
parent 5bbdb890c8
commit b7cc5e79b3
Signed by: alemi
GPG key ID: A4895B84D311642C
15 changed files with 122 additions and 158 deletions

2
.tci
View file

@ -12,7 +12,7 @@ echo "restarting service"
systemctl --user start upub
echo "rebuilding frontend"
cd web
CARGO_BUILD_JOBS=4 /opt/bin/trunk build --release --public-url 'https://dev.upub.social/web'
CARGO_BUILD_JOBS=4 /opt/bin/trunk build --profile=wasm-release --public-url 'https://dev.upub.social/web'
echo "deploying frontend"
rm /srv/http/upub/dev/web/*
mv ./dist/* /srv/http/upub/dev/web/

72
Cargo.lock generated
View file

@ -270,44 +270,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [
"async-trait",
"axum-core 0.4.5",
"axum-core",
"bytes",
"futures-util",
"http 1.2.0",
"http-body",
"http-body-util",
"itoa",
"matchit 0.7.3",
"memchr",
"mime",
"multer",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
dependencies = [
"axum-core 0.5.0",
"bytes",
"form_urlencoded",
"futures-util",
"http 1.2.0",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit 0.8.4",
"matchit",
"memchr",
"mime",
"multer",
@ -344,25 +316,6 @@ dependencies = [
"sync_wrapper",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
dependencies = [
"bytes",
"futures-util",
"http 1.2.0",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
@ -1710,7 +1663,7 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
name = "httpsign"
version = "0.1.0"
dependencies = [
"axum 0.8.1",
"axum",
"base64",
"openssl",
"thiserror 2.0.11",
@ -2137,7 +2090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b613d5784037baee42a11d21bc263adfc1a55e416556a3d5bfe39c7b87fadf"
dependencies = [
"any_spawner",
"axum 0.7.9",
"axum",
"dashmap",
"futures",
"hydration_context",
@ -2452,12 +2405,6 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "md-5"
version = "0.10.6"
@ -3881,7 +3828,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5dd7fcccd3ef2081da086c1f8595b506627abbbbc9f64be0141d2251219570e"
dependencies = [
"axum 0.7.9",
"axum",
"bytes",
"const_format",
"dashmap",
@ -5124,11 +5071,13 @@ dependencies = [
"apb",
"async-recursion",
"async-trait",
"axum",
"base64",
"chrono",
"hmac",
"httpsign",
"jrd",
"leptos_config",
"mdhtml",
"nodeinfo",
"openssl",
@ -5197,14 +5146,12 @@ name = "upub-routes"
version = "0.3.0"
dependencies = [
"apb",
"axum 0.8.1",
"axum",
"chrono",
"httpsign",
"jrd",
"leptos",
"leptos_axum",
"leptos_meta",
"leptos_router",
"leptos_config",
"mastodon-async-entities",
"nodeinfo",
"rand",
@ -5219,6 +5166,7 @@ dependencies = [
"tower-http",
"tracing",
"upub",
"upub-web",
]
[[package]]

View file

@ -24,7 +24,7 @@ repository = "https://git.alemi.dev/upub.git"
readme = "README.md"
[[bin]]
name = "upub"
name = "upub-bin"
path = "main.rs"
[dependencies]
@ -50,8 +50,14 @@ serve = ["dep:upub-routes"]
migrate = ["dep:upub-migrations"]
cli = ["dep:upub-cli"]
worker = ["dep:upub-worker"]
web = []
web-build-fe = []
web = ["upub/web", "upub-routes?/web"]
[[workspace.metadata.leptos]]
name = "upub"
bin-package = "upub-bin"
bin-features = ["serve", "migrate", "cli", "worker", "web"]
lib-package = "upub-web"
lib-features = ["leptos-hydrate"]
[profile.wasm-release]
inherits = "release"

View file

@ -1,12 +0,0 @@
fn main() {
#[cfg(all(feature = "web", feature = "web-build-fe"))]
{
println!("cargo:warning=running sub-process to build frontend");
let status = std::process::Command::new("cargo")
.current_dir("web")
.args(["build", "--profile=wasm-release", "--target=wasm32-unknown-unknown"])
.status()
.unwrap();
assert!(status.success(), "failed building wasm bundle");
}
}

View file

@ -36,3 +36,9 @@ reqwest = { version = "0.12", features = ["json"] }
apb = { path = "../apb", features = ["unstructured", "orm", "did-core", "activitypub-miscellaneous-terms", "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" }
leptos_config = { version = "0.7", optional = true }
axum = { version = "0.7", optional = true }
[features]
default = []
web = ["dep:leptos_config", "dep:axum"]

View file

@ -193,3 +193,30 @@ pub enum Internal {
Activity(i64),
Actor(i64),
}
#[cfg(feature = "web")]
mod leptos_state {
impl axum::extract::FromRef<super::Context> for leptos_config::LeptosOptions {
fn from_ref(_ctx: &super::Context) -> leptos_config::LeptosOptions {
static CONF: std::sync::OnceLock<leptos_config::LeptosOptions> = std::sync::OnceLock::new();
CONF.get_or_init(||
leptos_config::LeptosOptions {
env: {
#[cfg(debug_assertions)]{ leptos_config::Env::DEV }
#[cfg(not(debug_assertions))] { leptos_config::Env::PROD }
},
output_name: "upub_web".into(),
site_root: "web/dist".into(),
site_pkg_dir: "pkg".into(),
site_addr: "127.0.0.1:3000/web".parse().expect("could not create socket addr"), // TODO we don't want to serve? what is this for??
reload_port: 3001,
reload_external_port: None,
reload_ws_protocol: leptos_config::ReloadWSProtocol::WS,
not_found_path: "web/404.html".into(),
hash_file: "hash.txt".into(),
hash_files: true,
}
).clone()
}
}
}

View file

@ -22,7 +22,7 @@ jrd = "0.1"
tracing = "0.1"
tokio = "1.43"
reqwest = { version = "0.12", features = ["json"] }
axum = { version = "0.8", features = ["multipart"] }
axum = { version = "0.7", features = ["multipart"] }
tower-http = { version = "0.6", features = ["cors", "trace"] }
httpsign = { path = "../utils/httpsign/", features = ["axum"] }
apb = { path = "../apb", features = ["unstructured", "orm", "activitypub-fe", "activitypub-counters", "litepub", "ostatus", "toot", "jsonld"] }
@ -33,18 +33,12 @@ nodeinfo = { git = "https://codeberg.org/thefederationinfo/nodeinfo-rs", rev = "
mastodon-async-entities = { version = "1.1.0", optional = true }
time = { version = "0.3", features = ["serde"], optional = true }
# frontend
leptos = { version = "0.7", optional = true }
leptos_router = { version = "0.7", optional = true }
leptos_axum = { version = "0.7", optional = true }
leptos_meta = { version = "0.7", optional = true }
leptos_config = { version = "0.7", optional = true }
upub-web = { path = "../web", default-features = false, optional = true }
[features]
default = ["activitypub", "web"]
default = ["activitypub"]
activitypub = []
mastodon = ["dep:mastodon-async-entities"]
web = [
"dep:leptos",
"dep:leptos_router",
"dep:leptos_axum",
"dep:leptos_meta"
]
web = ["dep:leptos_axum", "dep:leptos_config", "dep:upub-web", "upub-web?/leptos-ssr", "upub/web"]

View file

@ -22,7 +22,7 @@ impl super::ActivityPubRouter for Router<upub::Context> {
// fetch route, to debug and retreive remote objects
.route("/search", get(ap::application::search))
.route("/fetch", get(ap::application::ap_fetch))
.route("/proxy/{hmac}/{uri}", get(ap::application::cloak_proxy))
.route("/proxy/:hmac/:uri", get(ap::application::cloak_proxy))
.route("/inbox", post(ap::inbox::post))
.route("/inbox", get(ap::inbox::get))
.route("/inbox/page", get(ap::inbox::page))
@ -39,49 +39,49 @@ impl super::ActivityPubRouter for Router<upub::Context> {
.route("/.well-known/host-meta", get(ap::well_known::host_meta))
.route("/.well-known/nodeinfo", get(ap::well_known::nodeinfo_discovery))
.route("/.well-known/oauth-authorization-server", get(ap::well_known::oauth_authorization_server))
.route("/nodeinfo/{version}", get(ap::well_known::nodeinfo))
.route("/nodeinfo/:version", get(ap::well_known::nodeinfo))
// actor routes
.route("/actors/{id}", get(ap::actor::view))
.route("/actors/{id}/inbox", post(ap::actor::inbox::post))
.route("/actors/{id}/inbox", get(ap::actor::inbox::get))
.route("/actors/{id}/inbox/page", get(ap::actor::inbox::page))
.route("/actors/{id}/outbox", post(ap::actor::outbox::post))
.route("/actors/{id}/outbox", get(ap::actor::outbox::get))
.route("/actors/{id}/outbox/page", get(ap::actor::outbox::page))
.route("/actors/{id}/notifications", get(ap::actor::notifications::get))
.route("/actors/{id}/notifications/page", get(ap::actor::notifications::page))
.route("/actors/{id}/followers", get(ap::actor::following::get::<false>))
.route("/actors/{id}/followers/page", get(ap::actor::following::page::<false>))
.route("/actors/{id}/following", get(ap::actor::following::get::<true>))
.route("/actors/{id}/following/page", get(ap::actor::following::page::<true>))
.route("/actors/{id}/likes", get(ap::actor::likes::get))
.route("/actors/{id}/likes/page", get(ap::actor::likes::page))
.route("/actors/:id", get(ap::actor::view))
.route("/actors/:id/inbox", post(ap::actor::inbox::post))
.route("/actors/:id/inbox", get(ap::actor::inbox::get))
.route("/actors/:id/inbox/page", get(ap::actor::inbox::page))
.route("/actors/:id/outbox", post(ap::actor::outbox::post))
.route("/actors/:id/outbox", get(ap::actor::outbox::get))
.route("/actors/:id/outbox/page", get(ap::actor::outbox::page))
.route("/actors/:id/notifications", get(ap::actor::notifications::get))
.route("/actors/:id/notifications/page", get(ap::actor::notifications::page))
.route("/actors/:id/followers", get(ap::actor::following::get::<false>))
.route("/actors/:id/followers/page", get(ap::actor::following::page::<false>))
.route("/actors/:id/following", get(ap::actor::following::get::<true>))
.route("/actors/:id/following/page", get(ap::actor::following::page::<true>))
.route("/actors/:id/likes", get(ap::actor::likes::get))
.route("/actors/:id/likes/page", get(ap::actor::likes::page))
.route("/groups", get(ap::groups::get))
.route("/groups/page", get(ap::groups::page))
// .route("/actors/{id}/audience", get(ap::actor::audience::get))
// .route("/actors/{id}/audience/page", get(ap::actor::audience::page))
// .route("/actors/:id/audience", get(ap::actor::audience::get))
// .route("/actors/:id/audience/page", get(ap::actor::audience::page))
// activities
.route("/activities/{id}", get(ap::activity::view))
.route("/activities/:id", get(ap::activity::view))
// hashtags
.route("/tags/{id}", get(ap::tags::get))
.route("/tags/{id}/page", get(ap::tags::page))
.route("/tags/:id", get(ap::tags::get))
.route("/tags/:id/page", get(ap::tags::page))
// specific object routes
.route("/objects/{id}", get(ap::object::view))
.route("/objects/{id}/replies", get(ap::object::replies::get))
.route("/objects/{id}/replies/page", get(ap::object::replies::page))
.route("/objects/{id}/context", get(ap::object::context::get))
.route("/objects/{id}/context/page", get(ap::object::context::page))
.route("/objects/{id}/likes", get(ap::object::likes::get))
.route("/objects/{id}/likes/page", get(ap::object::likes::page))
.route("/objects/{id}/shares", get(ap::object::shares::get))
.route("/objects/{id}/shares/page", get(ap::object::shares::page))
.route("/objects/:id", get(ap::object::view))
.route("/objects/:id/replies", get(ap::object::replies::get))
.route("/objects/:id/replies/page", get(ap::object::replies::page))
.route("/objects/:id/context", get(ap::object::context::get))
.route("/objects/:id/context/page", get(ap::object::context::page))
.route("/objects/:id/likes", get(ap::object::likes::get))
.route("/objects/:id/likes/page", get(ap::object::likes::page))
.route("/objects/:id/shares", get(ap::object::shares::get))
.route("/objects/:id/shares/page", get(ap::object::shares::page))
// file routes
.route("/file", post(ap::file::upload))
.route("/file/{id}", get(ap::file::download))
//.route("/objects/{id}/likes", get(ap::object::likes::get))
//.route("/objects/{id}/likes/page", get(ap::object::likes::page))
//.route("/objects/{id}/shares", get(ap::object::announces::get))
//.route("/objects/{id}/shares/page", get(ap::object::announces::page))
.route("/file/:id", get(ap::file::download))
//.route("/objects/:id/likes", get(ap::object::likes::get))
//.route("/objects/:id/likes/page", get(ap::object::likes::page))
//.route("/objects/:id/shares", get(ap::object::announces::get))
//.route("/objects/:id/shares/page", get(ap::object::announces::page))
}
}

View file

@ -82,6 +82,7 @@ impl Identity {
pub struct AuthIdentity(pub Identity);
#[axum::async_trait]
impl<S> FromRequestParts<S> for AuthIdentity
where
upub::Context: FromRef<S>,

View file

@ -32,7 +32,7 @@ pub mod mastodon { impl super::MastodonRouter for axum::Router<upub::Context> {}
pub trait WebRouter {
fn web_routes(self) -> Self where Self: Sized { self }
fn web_routes(self, _ctx: &upub::Context) -> Self where Self: Sized { self }
}
#[cfg(feature = "web")]
@ -43,7 +43,6 @@ pub mod web {
impl super::WebRouter for axum::Router<upub::Context> {}
}
pub async fn serve(ctx: upub::Context, bind: String, shutdown: impl ShutdownToken) -> Result<(), std::io::Error> {
use tower_http::{cors::CorsLayer, trace::TraceLayer};
@ -62,8 +61,8 @@ pub async fn serve(ctx: upub::Context, bind: String, shutdown: impl ShutdownToke
})
)
.ap_routes()
.mastodon_routes() // no-op if mastodon feature is disabled
.web_routes() // no-op if web feature is disabled
.mastodon_routes()
.web_routes(&ctx)
.layer(CorsLayer::permissive())
.with_state(ctx);

View file

@ -1,27 +0,0 @@
use axum::{response::IntoResponse, routing::get, Router};
impl super::WebRouter for Router<upub::Context> {
fn web_routes(self) -> Self {
self
.route("/web/assets/upub-web.wasm", get(upub_web_wasm))
.route("/web/assets/style.css", get(upub_style_css))
.route("/web", get(upub_web_index))
.route("/web/", get(upub_web_index))
.route("/web/{*any}", get(upub_web_index))
}
}
async fn upub_web_wasm() -> impl IntoResponse {
include_bytes!("../../target/wasm32-unknown-unknown/wasm-release/upub-web.wasm")
}
async fn upub_style_css() -> impl IntoResponse {
include_str!("../../web/assets/style.css")
}
async fn upub_web_index() -> impl IntoResponse {
include_str!("../../web/index.html")
}

11
routes/src/web/mod.rs Normal file
View file

@ -0,0 +1,11 @@
use leptos_axum::LeptosRoutes;
impl super::WebRouter for axum::Router<upub::Context> {
fn web_routes(self, ctx: &upub::Context) -> Self where Self: Sized {
self.leptos_routes(
ctx,
leptos_axum::generate_route_list(upub_web::App),
move || ""
)
}
}

View file

@ -19,7 +19,7 @@ thiserror = "2.0"
tracing = "0.1"
base64 = "0.22"
openssl = "0.10" # TODO handle pubkeys with a smaller crate
axum = { version = "0.8", optional = true }
axum = { version = "0.7", optional = true }
[features]
default = []

View file

@ -9,6 +9,9 @@ keywords = ["activitypub", "upub", "json", "web", "wasm"]
repository = "https://git.alemi.dev/upub.git"
#readme = "README.md"
[lib]
crate-type = ["rlib", "cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
@ -24,7 +27,7 @@ serde_json = "1.0"
serde_default = "0.2"
serde-inline-default = "0.2"
dashmap = "6.1"
leptos = { version = "0.7", features = ["csr", "tracing"] }
leptos = { version = "0.7", features = ["tracing"] }
leptos_router = { version = "0.7", features = ["tracing"] }
leptos-use = "0.15"
codee = { version = "0.2", features = ["json_serde"] } # WHYYY LEPTOS-USE AKSJFOASHGOAEG
@ -38,3 +41,9 @@ jrd = "0.1"
tld = "2.36"
web-sys = { version = "0.3", features = ["Screen"] }
regex = "1.11"
[features]
default = ["leptos-csr"]
leptos-ssr = ["leptos/ssr"]
leptos-csr = ["leptos/csr"]
leptos-hydrate = ["leptos/hydrate"]

View file

@ -1,3 +1,5 @@
#![recursion_limit = "256"] // oh nooo leptos...
mod auth;
mod app;
mod components;