diff --git a/.env b/.env
index c60e236..8332a0a 100644
--- a/.env
+++ b/.env
@@ -1 +1,2 @@
+OUT_DIR="compiled_templates"
DATABASE_URL=postgres://ap_actix:ap_actix@localhost:5432/ap_actix
diff --git a/Cargo.lock b/Cargo.lock
index 1e63911..9947ae0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -581,6 +581,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+[[package]]
+name = "bytecount"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0017894339f586ccb943b01b9555de56770c11cda818e7e3d8bd93f4ed7f46e"
+
[[package]]
name = "byteorder"
version = "1.3.4"
@@ -1160,6 +1166,15 @@ dependencies = [
"winreg",
]
+[[package]]
+name = "itertools"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itoa"
version = "0.4.5"
@@ -1443,6 +1458,17 @@ dependencies = [
"num-traits 0.2.11",
]
+[[package]]
+name = "num-rational"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
+dependencies = [
+ "autocfg 1.0.0",
+ "num-integer",
+ "num-traits 0.2.11",
+]
+
[[package]]
name = "num-traits"
version = "0.1.43"
@@ -1764,12 +1790,14 @@ dependencies = [
"http-signature-normalization-actix",
"log",
"lru",
+ "mime",
"num_cpus",
"pretty_env_logger",
"rand",
"rsa",
"rsa-magic-public-key",
"rsa-pem",
+ "ructe",
"serde 1.0.105",
"serde_json",
"sha2",
@@ -1850,6 +1878,35 @@ dependencies = [
"yasna",
]
+[[package]]
+name = "rsass"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53314103d427bab245db7b3d1602faf509d2dd03db85b32421d1748e93bb4475"
+dependencies = [
+ "bytecount",
+ "lazy_static",
+ "nom",
+ "num-rational",
+ "num-traits 0.2.11",
+ "rand",
+]
+
+[[package]]
+name = "ructe"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c85620b8046f88a870d93d90fa56904dec76cc79139bfcc22e71e87f0cd2169f"
+dependencies = [
+ "base64 0.11.0",
+ "bytecount",
+ "itertools",
+ "md5",
+ "mime",
+ "nom",
+ "rsass",
+]
+
[[package]]
name = "rust-ini"
version = "0.13.0"
diff --git a/Cargo.toml b/Cargo.toml
index 6bd4503..8613906 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ readme = "README.md"
repository = "https://git.asonix.dog/asonix/ap-relay"
keywords = ["activitypub", "relay"]
edition = "2018"
+build = "src/build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -27,6 +28,7 @@ futures = "0.3.4"
http-signature-normalization-actix = { version = "0.3.0-alpha.7", default-features = false, features = ["sha-2"] }
log = "0.4"
lru = "0.4.3"
+mime = "0.3.16"
num_cpus = "1.12"
pretty_env_logger = "0.4.0"
rand = "0.7"
@@ -42,5 +44,10 @@ tokio = { version = "0.2.13", features = ["sync"] }
ttl_cache = "0.5.1"
uuid = { version = "0.8", features = ["v4"] }
+[build-dependencies]
+anyhow = "1.0"
+dotenv = "0.15.0"
+ructe = { version = "0.9.2", features = ["sass", "mime03"] }
+
[profile.dev.package.rsa]
opt-level = 3
diff --git a/scss/index.scss b/scss/index.scss
new file mode 100644
index 0000000..f89982a
--- /dev/null
+++ b/scss/index.scss
@@ -0,0 +1,82 @@
+body {
+ background-color: #f5f5f5;
+ font-family: sans-serif;
+ margin: 0;
+ position: relative;
+ min-height: 100vh;
+ padding-bottom: 96px;
+}
+
+body,
+body * {
+ box-sizing: border-box;
+}
+
+header {
+ padding: 32px 0;
+ background-color: #333;
+ color: #f5f5f5;
+ text-align: center;
+
+ h1 {
+ margin: 0px;
+ }
+}
+
+section {
+ padding: 24px;
+ background-color: #fff;
+ border: 1px solid #e5e5e5;
+ border-radius: 3px;
+ margin: 32px auto 0;
+ max-width: 700px;
+
+ h3 {
+ margin-top: 0px;
+ }
+}
+
+pre {
+ border: 1px solid #e5e5e5;
+ border-radius: 3px;
+ background-color: #f5f5f5;
+ padding: 8px;
+ padding-left: 32px;
+ padding-top: 10px;
+ position: relative;
+
+ &:before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 24px;
+ background-color: #e5e5e5;
+ }
+}
+
+footer {
+ background-color: #333;
+ color: #f5f5f5;
+ position: absolute;
+ padding: 16px 8px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ text-align: center;
+
+ a {
+ &,
+ &:focus,
+ &:hover,
+ &:active {
+ color: #ea7fbc;
+ }
+ }
+
+ p {
+ margin: 0;
+ }
+}
diff --git a/src/build.rs b/src/build.rs
new file mode 100644
index 0000000..4430193
--- /dev/null
+++ b/src/build.rs
@@ -0,0 +1,12 @@
+use ructe::Ructe;
+
+fn main() -> Result<(), anyhow::Error> {
+ dotenv::dotenv().ok();
+
+ let mut ructe = Ructe::from_env()?;
+ let mut statics = ructe.statics()?;
+ statics.add_sass_file("scss/index.scss")?;
+ ructe.compile_templates("templates")?;
+
+ Ok(())
+}
diff --git a/src/error.rs b/src/error.rs
index eacc97f..23253a1 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -64,6 +64,9 @@ pub enum MyError {
#[error("Too many CPUs, {0}")]
CpuCount(#[from] std::num::TryFromIntError),
+ #[error("Couldn't flush buffer")]
+ FlushBuffer,
+
#[error("Timed out while waiting on db pool")]
DbTimeout,
diff --git a/src/main.rs b/src/main.rs
index 74e0b49..ca3a204 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,14 @@
-use actix_web::{middleware::Logger, web, App, HttpServer, Responder};
+use actix_web::{
+ http::header::{ContentType, Expires},
+ middleware::Logger,
+ web, App, HttpResponse, HttpServer,
+};
use bb8_postgres::tokio_postgres;
+use log::error;
+use std::{
+ io::BufWriter,
+ time::{Duration, SystemTime},
+};
mod actor;
mod apub;
@@ -17,32 +26,42 @@ mod state;
mod verifier;
mod webfinger;
-use self::{args::Args, config::Config, db::Db, state::State, webfinger::RelayResolver};
-
-async fn index(state: web::Data, config: web::Data) -> impl Responder {
- let mut s = String::new();
- s.push_str(&format!("Welcome to the relay on {}\n", config.hostname()));
+use self::{
+ args::Args, config::Config, db::Db, error::MyError, state::State,
+ templates::statics::StaticFile, webfinger::RelayResolver,
+};
+async fn index(
+ state: web::Data,
+ config: web::Data,
+) -> Result {
let listeners = state.listeners().await;
- if listeners.is_empty() {
- s.push_str("There are no currently connected servers\n");
+
+ let mut buf = BufWriter::new(Vec::new());
+
+ templates::index(&mut buf, &listeners, &config)?;
+ let buf = buf.into_inner().map_err(|e| {
+ error!("Error rendering template, {}", e.error());
+ MyError::FlushBuffer
+ })?;
+
+ Ok(HttpResponse::Ok().content_type("text/html").body(buf))
+}
+
+static FAR: Duration = Duration::from_secs(60 * 60 * 24);
+
+async fn static_file(filename: web::Path) -> HttpResponse {
+ if let Some(data) = StaticFile::get(&filename.into_inner()) {
+ let far_expires = SystemTime::now() + FAR;
+ HttpResponse::Ok()
+ .set(Expires(far_expires.into()))
+ .set(ContentType(data.mime.clone()))
+ .body(data.content)
} else {
- s.push_str("Here are the currently connected servers:\n");
- s.push_str("\n");
+ HttpResponse::NotFound()
+ .reason("No such static file.")
+ .finish()
}
-
- for listener in listeners {
- if let Some(domain) = listener.as_url().domain() {
- s.push_str(&format!("{}\n", domain));
- }
- }
- s.push_str("\n");
- s.push_str(&format!(
- "The source code for this project can be found at {}\n",
- config.source_code()
- ));
-
- s
}
#[actix_rt::main]
@@ -107,9 +126,12 @@ async fn main() -> Result<(), anyhow::Error> {
.service(actix_webfinger::scoped::<_, RelayResolver>())
.service(web::resource("/nodeinfo").route(web::get().to(nodeinfo::well_known))),
)
+ .service(web::resource("/static/{filename}").route(web::get().to(static_file)))
})
.bind(bind_address)?
.run()
.await?;
Ok(())
}
+
+include!(concat!(env!("OUT_DIR"), "/templates.rs"));
diff --git a/templates/index.rs.html b/templates/index.rs.html
new file mode 100644
index 0000000..e97af0b
--- /dev/null
+++ b/templates/index.rs.html
@@ -0,0 +1,64 @@
+@use crate::{config::{Config, UrlKind}, templates::statics::index_css};
+@use activitystreams::primitives::XsdAnyUri;
+
+@(listeners: &[XsdAnyUri], config: &Config)
+
+
+
+
+
+ @config.hostname() | ActivityPub Relay
+
+
+
+
+ Welcome to @config.software_name() on @config.hostname()
+
+
+
+ Connected Servers:
+ @if listeners.is_empty() {
+ There are no connected servers at this time.
+ } else {
+
+ @for listener in listeners {
+ @if let Some(domain) = listener.as_url().domain() {
+ - @domain
+ }
+ }
+
+ }
+
+
+ Joining
+
+ If you are the admin of a server that supports activitypub relays, you can add
+ this relay to your server.
+
+ Mastodon
+
+ Mastodon admins can add this relay by adding
+
@config.generate_url(UrlKind::Inbox)
in their relay settings.
+
+ Pleroma
+
+ Pleroma admins can add this relay by adding
+
@config.generate_url(UrlKind::Actor)
+ to their relay settings (I don't actually know how pleroma handles adding
+ relays, is it still a mix command?).
+
+ Others
+
+ Consult the documentation for your server. It's likely that it follows either
+ Mastodon or Pleroma's relay formatting.
+
+
+
+
+
+