Add media cache, improve default styles

This commit is contained in:
asonix 2020-03-25 22:26:45 -05:00
parent 0a42450801
commit 9ada30626b
18 changed files with 613 additions and 65 deletions

178
Cargo.lock generated
View file

@ -364,6 +364,21 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "ammonia"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89eac85170f4b3fb3dc5e442c1cfb036cb8eecf9dbbd431a161ffad15d90ea3b"
dependencies = [
"html5ever",
"lazy_static",
"maplit",
"markup5ever_rcdom",
"matches",
"tendril",
"url",
]
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.11.0" version = "0.11.0"
@ -930,6 +945,16 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
dependencies = [
"mac",
"new_debug_unreachable",
]
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.4" version = "0.3.4"
@ -1118,6 +1143,20 @@ dependencies = [
"winapi 0.3.8", "winapi 0.3.8",
] ]
[[package]]
name = "html5ever"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b"
dependencies = [
"log",
"mac",
"markup5ever",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.0" version = "0.2.0"
@ -1339,6 +1378,47 @@ dependencies = [
"linked-hash-map 0.5.2", "linked-hash-map 0.5.2",
] ]
[[package]]
name = "mac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "markup5ever"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab"
dependencies = [
"log",
"phf",
"phf_codegen",
"serde 1.0.105",
"serde_derive",
"serde_json",
"string_cache",
"string_cache_codegen",
"tendril",
]
[[package]]
name = "markup5ever_rcdom"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b"
dependencies = [
"html5ever",
"markup5ever",
"tendril",
"xml5ever",
]
[[package]] [[package]]
name = "match_cfg" name = "match_cfg"
version = "0.1.0" version = "0.1.0"
@ -1437,6 +1517,12 @@ dependencies = [
"winapi 0.3.8", "winapi 0.3.8",
] ]
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]] [[package]]
name = "nodrop" name = "nodrop"
version = "0.1.14" version = "0.1.14"
@ -1600,6 +1686,26 @@ dependencies = [
"phf_shared", "phf_shared",
] ]
[[package]]
name = "phf_codegen"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand",
]
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.8.0" version = "0.8.0"
@ -1680,6 +1786,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]] [[package]]
name = "pretty_env_logger" name = "pretty_env_logger"
version = "0.4.0" version = "0.4.0"
@ -1763,6 +1875,7 @@ dependencies = [
"rand_chacha", "rand_chacha",
"rand_core", "rand_core",
"rand_hc", "rand_hc",
"rand_pcg",
] ]
[[package]] [[package]]
@ -1793,6 +1906,15 @@ dependencies = [
"rand_core", "rand_core",
] ]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.1.56" version = "0.1.56"
@ -1826,12 +1948,14 @@ dependencies = [
"actix-rt", "actix-rt",
"actix-web", "actix-web",
"actix-webfinger", "actix-webfinger",
"ammonia",
"anyhow", "anyhow",
"async-trait", "async-trait",
"background-jobs", "background-jobs",
"background-jobs-core", "background-jobs-core",
"base64 0.12.0", "base64 0.12.0",
"bb8-postgres", "bb8-postgres",
"bytes",
"config", "config",
"dotenv", "dotenv",
"env_logger", "env_logger",
@ -2246,6 +2370,31 @@ 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 = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "string_cache"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"phf_shared",
"precomputed-hash",
"serde 1.0.105",
]
[[package]]
name = "string_cache_codegen"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "stringprep" name = "stringprep"
version = "0.1.2" version = "0.1.2"
@ -2332,6 +2481,17 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "tendril"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707feda9f2582d5d680d733e38755547a3e8fb471e7ba11452ecfd9ce93a5d3b"
dependencies = [
"futf",
"mac",
"utf-8",
]
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.1.0" version = "1.1.0"
@ -2636,6 +2796,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf-8"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "0.8.1" version = "0.8.1"
@ -2815,6 +2981,18 @@ dependencies = [
"winapi-build", "winapi-build",
] ]
[[package]]
name = "xml5ever"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59"
dependencies = [
"log",
"mac",
"markup5ever",
"time 0.1.42",
]
[[package]] [[package]]
name = "yaml-rust" name = "yaml-rust"
version = "0.4.3" version = "0.4.3"

View file

@ -19,9 +19,11 @@ actix-rt = "1.0.0"
actix-web = { version = "3.0.0-alpha.1", features = ["rustls"] } actix-web = { version = "3.0.0-alpha.1", features = ["rustls"] }
actix-webfinger = "0.3.0-alpha.3" actix-webfinger = "0.3.0-alpha.3"
activitystreams = "0.5.0-alpha.11" activitystreams = "0.5.0-alpha.11"
ammonia = "3.1.0"
async-trait = "0.1.24" async-trait = "0.1.24"
background-jobs = { version = "0.8.0-alpha.0", git = "https://git.asonix.dog/Aardwolf/background-jobs", default-features = false, features = ["background-jobs-actix"] } background-jobs = { version = "0.8.0-alpha.0", git = "https://git.asonix.dog/Aardwolf/background-jobs", default-features = false, features = ["background-jobs-actix"] }
background-jobs-core = { version = "0.7.0", git = "https://git.asonix.dog/Aardwolf/background-jobs" } background-jobs-core = { version = "0.7.0", git = "https://git.asonix.dog/Aardwolf/background-jobs" }
bytes = "0.5.4"
base64 = "0.12" base64 = "0.12"
bb8-postgres = { version = "0.4.0", features = ["with-serde_json-1", "with-uuid-0_8", "with-chrono-0_4"] } bb8-postgres = { version = "0.4.0", features = ["with-serde_json-1", "with-uuid-0_8", "with-chrono-0_4"] }
config = "0.10.1" config = "0.10.1"

View file

@ -7,32 +7,69 @@ body {
padding-bottom: 96px; padding-bottom: 96px;
} }
ul {
margin: 0;
padding: 0;
list-style: none;
}
body, body,
body * { body * {
box-sizing: border-box; box-sizing: border-box;
} }
header { header {
padding: 32px 0;
background-color: #333; background-color: #333;
color: #f5f5f5; color: #f5f5f5;
text-align: center;
.header-text {
max-width: 700px;
margin: auto;
padding: 24px 0;
}
h1 { h1 {
margin: 0px; margin: 0px;
} }
p {
margin: 0;
margin-top: 8px;
font-style: italic;
}
} }
section { section {
padding: 24px;
background-color: #fff; background-color: #fff;
border: 1px solid #e5e5e5; border: 1px solid #e5e5e5;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.1);
border-radius: 3px; border-radius: 3px;
margin: 32px auto 0; margin: 32px auto 0;
max-width: 700px; max-width: 700px;
padding-bottom: 32px;
> p:first-child {
margin-top: 0;
}
h3 { h3 {
margin-top: 0px; padding: 24px;
margin: 0px;
}
li {
padding-top: 24px;
padding-bottom: 24px;
border-top: 1px solid #e5e5e5;
}
.padded {
padding: 0 24px;
}
.joining {
padding: 24px;
border-top: 1px solid #e5e5e5;
} }
} }
@ -57,6 +94,15 @@ pre {
} }
} }
a {
&,
&:focus,
&:hover,
&:active {
color: #ea7fbc;
}
}
footer { footer {
background-color: #333; background-color: #333;
color: #f5f5f5; color: #f5f5f5;
@ -67,16 +113,78 @@ footer {
right: 0; right: 0;
text-align: center; text-align: center;
a {
&,
&:focus,
&:hover,
&:active {
color: #ea7fbc;
}
}
p { p {
margin: 0; margin: 0;
} }
} }
.instance,
.info {
h4 {
font-size: 20px;
margin: 0;
}
.instance-info {
background-color: #f5f5f5;
border-top: 1px solid #e5e5e5;
border-bottom: 1px solid #e5e5e5;
padding: 32px;
.instance-description {
margin: 0;
margin-bottom: 24px;
}
}
a {
text-decoration: none;
}
}
.admin {
margin-top: 32px;
display: flex;
align-items: center;
background-color: #fff;
border: 1px solid #e5e5e5;
border-radius: 3px;
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
.display-name {
font-weight: 600;
font-size: 16px;
margin: 0;
}
.username {
font-size: 14px;
color: #777;
margin: 0;
margin-top: 8px;
}
}
.avatar {
width: 80px;
height: 80px;
img {
width: 100%;
border-radius: 40px;
border: 1px solid #333;
background-color: #f5f5f5;
box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.1);
}
}
@media(max-width: 700px) {
header .header-text {
padding: 24px;
}
section {
border-left: none;
border-right: none;
}
}

View file

@ -28,6 +28,7 @@ pub enum UrlKind {
Inbox, Inbox,
Index, Index,
MainKey, MainKey,
Media(Uuid),
NodeInfo, NodeInfo,
Outbox, Outbox,
} }
@ -136,6 +137,7 @@ impl Config {
UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname), UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname),
UrlKind::Index => format!("{}://{}/", scheme, self.hostname), UrlKind::Index => format!("{}://{}/", scheme, self.hostname),
UrlKind::MainKey => format!("{}://{}/actor#main-key", scheme, self.hostname), UrlKind::MainKey => format!("{}://{}/actor#main-key", scheme, self.hostname),
UrlKind::Media(uuid) => format!("{}://{}/media/{}", scheme, self.hostname, uuid),
UrlKind::NodeInfo => format!("{}://{}/nodeinfo/2.0.json", scheme, self.hostname), UrlKind::NodeInfo => format!("{}://{}/nodeinfo/2.0.json", scheme, self.hostname),
UrlKind::Outbox => format!("{}://{}/outbox", scheme, self.hostname), UrlKind::Outbox => format!("{}://{}/outbox", scheme, self.hostname),
} }

60
src/data/media.rs Normal file
View file

@ -0,0 +1,60 @@
use activitystreams::primitives::XsdAnyUri;
use bytes::Bytes;
use lru::LruCache;
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::{Mutex, RwLock};
use ttl_cache::TtlCache;
use uuid::Uuid;
static MEDIA_DURATION: Duration = Duration::from_secs(60 * 60 * 24 * 2);
#[derive(Clone)]
pub struct Media {
inverse: Arc<Mutex<HashMap<XsdAnyUri, Uuid>>>,
url_cache: Arc<Mutex<LruCache<Uuid, XsdAnyUri>>>,
byte_cache: Arc<RwLock<TtlCache<Uuid, (String, Bytes)>>>,
}
impl Media {
pub fn new() -> Self {
Media {
inverse: Arc::new(Mutex::new(HashMap::new())),
url_cache: Arc::new(Mutex::new(LruCache::new(128))),
byte_cache: Arc::new(RwLock::new(TtlCache::new(128))),
}
}
pub async fn get_uuid(&self, url: &XsdAnyUri) -> Option<Uuid> {
let uuid = self.inverse.lock().await.get(url).cloned()?;
if self.url_cache.lock().await.contains(&uuid) {
return Some(uuid);
}
self.inverse.lock().await.remove(url);
None
}
pub async fn get_url(&self, uuid: Uuid) -> Option<XsdAnyUri> {
self.url_cache.lock().await.get(&uuid).cloned()
}
pub async fn get_bytes(&self, uuid: Uuid) -> Option<(String, Bytes)> {
self.byte_cache.read().await.get(&uuid).cloned()
}
pub async fn store_url(&self, url: &XsdAnyUri) -> Uuid {
let uuid = Uuid::new_v4();
self.inverse.lock().await.insert(url.clone(), uuid);
self.url_cache.lock().await.put(uuid, url.clone());
uuid
}
pub async fn store_bytes(&self, uuid: Uuid, content_type: String, bytes: Bytes) {
self.byte_cache
.write()
.await
.insert(uuid, (content_type, bytes), MEDIA_DURATION);
}
}

View file

@ -1,9 +1,11 @@
mod actor; mod actor;
mod media;
mod node; mod node;
mod state; mod state;
pub use self::{ pub use self::{
actor::{Actor, ActorCache}, actor::{Actor, ActorCache},
node::{Node, NodeCache}, media::Media,
node::{Contact, Info, Instance, Node, NodeCache},
state::State, state::State,
}; };

View file

@ -70,6 +70,9 @@ pub enum MyError {
#[error("Hosts don't match, {0}, {1}")] #[error("Hosts don't match, {0}, {1}")]
HostMismatch(String, String), HostMismatch(String, String),
#[error("Invalid or missing content type")]
ContentType,
#[error("Couldn't flush buffer")] #[error("Couldn't flush buffer")]
FlushBuffer, FlushBuffer,

View file

@ -1,4 +1,4 @@
use crate::jobs::JobState; use crate::{config::UrlKind, jobs::JobState};
use activitystreams::primitives::XsdAnyUri; use activitystreams::primitives::XsdAnyUri;
use anyhow::Error; use anyhow::Error;
use background_jobs::{Job, Processor}; use background_jobs::{Job, Processor};
@ -44,7 +44,14 @@ impl QueryInstance {
instance.description instance.description
}; };
if let Some(contact) = instance.contact { if let Some(mut contact) = instance.contact {
if let Some(uuid) = state.media.get_uuid(&contact.avatar).await {
contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).parse()?;
} else {
let uuid = state.media.store_url(&contact.avatar).await;
contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).parse()?;
}
state state
.node_cache .node_cache
.set_contact( .set_contact(
@ -57,6 +64,8 @@ impl QueryInstance {
.await?; .await?;
} }
let description = ammonia::clean(&description);
state state
.node_cache .node_cache
.set_instance( .set_instance(

View file

@ -9,7 +9,8 @@ pub use self::{
}; };
use crate::{ use crate::{
data::{ActorCache, NodeCache, State}, config::Config,
data::{ActorCache, Media, NodeCache, State},
db::Db, db::Db,
error::MyError, error::MyError,
jobs::{ jobs::{
@ -33,10 +34,24 @@ pub fn create_server(db: Db) -> JobServer {
JobServer::new(shared) JobServer::new(shared)
} }
pub fn create_workers(state: State, actors: ActorCache, job_server: JobServer) { pub fn create_workers(
state: State,
actors: ActorCache,
job_server: JobServer,
media: Media,
config: Config,
) {
let remote_handle = job_server.remote.clone(); let remote_handle = job_server.remote.clone();
WorkerConfig::new(move || JobState::new(state.clone(), actors.clone(), job_server.clone())) WorkerConfig::new(move || {
JobState::new(
state.clone(),
actors.clone(),
job_server.clone(),
media.clone(),
config.clone(),
)
})
.register(DeliverProcessor) .register(DeliverProcessor)
.register(DeliverManyProcessor) .register(DeliverManyProcessor)
.register(NodeinfoProcessor) .register(NodeinfoProcessor)
@ -51,6 +66,8 @@ pub struct JobState {
requests: Requests, requests: Requests,
state: State, state: State,
actors: ActorCache, actors: ActorCache,
config: Config,
media: Media,
node_cache: NodeCache, node_cache: NodeCache,
job_server: JobServer, job_server: JobServer,
} }
@ -61,11 +78,19 @@ pub struct JobServer {
} }
impl JobState { impl JobState {
fn new(state: State, actors: ActorCache, job_server: JobServer) -> Self { fn new(
state: State,
actors: ActorCache,
job_server: JobServer,
media: Media,
config: Config,
) -> Self {
JobState { JobState {
requests: state.requests(), requests: state.requests(),
node_cache: state.node_cache(), node_cache: state.node_cache(),
actors, actors,
config,
media,
state, state,
job_server, job_server,
} }

View file

@ -16,7 +16,7 @@ mod routes;
use self::{ use self::{
args::Args, args::Args,
config::Config, config::Config,
data::{ActorCache, State}, data::{ActorCache, Media, State},
db::Db, db::Db,
jobs::{create_server, create_workers}, jobs::{create_server, create_workers},
middleware::RelayResolver, middleware::RelayResolver,
@ -62,6 +62,7 @@ async fn main() -> Result<(), anyhow::Error> {
return Ok(()); return Ok(());
} }
let media = Media::new();
let state = State::hydrate(config.clone(), &db).await?; let state = State::hydrate(config.clone(), &db).await?;
let actors = ActorCache::new(db.clone()); let actors = ActorCache::new(db.clone());
let job_server = create_server(db.clone()); let job_server = create_server(db.clone());
@ -84,9 +85,11 @@ async fn main() -> Result<(), anyhow::Error> {
let state = state.clone(); let state = state.clone();
let actors = actors.clone(); let actors = actors.clone();
let job_server = job_server.clone(); let job_server = job_server.clone();
let media = media.clone();
let config = config.clone();
Arbiter::new().exec_fn(move || { Arbiter::new().exec_fn(move || {
create_workers(state, actors, job_server); create_workers(state, actors, job_server, media, config);
}); });
} }
actix_rt::signal::ctrl_c().await?; actix_rt::signal::ctrl_c().await?;
@ -98,7 +101,13 @@ async fn main() -> Result<(), anyhow::Error> {
let bind_address = config.bind_address(); let bind_address = config.bind_address();
HttpServer::new(move || { HttpServer::new(move || {
if !no_jobs { if !no_jobs {
create_workers(state.clone(), actors.clone(), job_server.clone()); create_workers(
state.clone(),
actors.clone(),
job_server.clone(),
media.clone(),
config.clone(),
);
} }
App::new() App::new()
@ -109,7 +118,9 @@ async fn main() -> Result<(), anyhow::Error> {
.data(actors.clone()) .data(actors.clone())
.data(config.clone()) .data(config.clone())
.data(job_server.clone()) .data(job_server.clone())
.data(media.clone())
.service(web::resource("/").route(web::get().to(index))) .service(web::resource("/").route(web::get().to(index)))
.service(web::resource("/media/{path}").route(web::get().to(routes::media)))
.service( .service(
web::resource("/inbox") web::resource("/inbox")
.wrap(config.digest_middleware()) .wrap(config.digest_middleware())

View file

@ -1,6 +1,7 @@
use crate::error::MyError; use crate::error::MyError;
use activitystreams::primitives::XsdAnyUri; use activitystreams::primitives::XsdAnyUri;
use actix_web::client::Client; use actix_web::client::Client;
use bytes::Bytes;
use http_signature_normalization_actix::prelude::*; use http_signature_normalization_actix::prelude::*;
use log::error; use log::error;
use rsa::{hash::Hashes, padding::PaddingScheme, RSAPrivateKey}; use rsa::{hash::Hashes, padding::PaddingScheme, RSAPrivateKey};
@ -63,6 +64,55 @@ impl Requests {
}) })
} }
pub async fn fetch_bytes(&self, url: &str) -> Result<(String, Bytes), MyError> {
let mut res = self
.client
.get(url)
.header("Accept", "application/activity+json")
.header("User-Agent", self.user_agent.as_str())
.signature(&self.config, &self.key_id, |signing_string| {
self.sign(signing_string)
})?
.send()
.await
.map_err(|e| {
error!("Couldn't send request to {}, {}", url, e);
MyError::SendRequest
})?;
let content_type = if let Some(content_type) = res.headers().get("content-type") {
if let Ok(s) = content_type.to_str() {
s.to_owned()
} else {
return Err(MyError::ContentType);
}
} else {
return Err(MyError::ContentType);
};
if !res.status().is_success() {
if let Ok(bytes) = res.body().await {
if let Ok(s) = String::from_utf8(bytes.as_ref().to_vec()) {
if !s.is_empty() {
error!("Response, {}", s);
}
}
}
return Err(MyError::Status(res.status()));
}
let bytes = match res.body().limit(1024 * 1024 * 4).await {
Err(e) => {
error!("Coudn't fetch json from {}, {}", url, e);
return Err(MyError::ReceiveResponse);
}
Ok(bytes) => bytes,
};
Ok((content_type, bytes))
}
pub async fn deliver<T>(&self, inbox: XsdAnyUri, item: &T) -> Result<(), MyError> pub async fn deliver<T>(&self, inbox: XsdAnyUri, item: &T) -> Result<(), MyError>
where where
T: serde::ser::Serialize, T: serde::ser::Serialize,

View file

@ -8,7 +8,6 @@ pub async fn route(
config: web::Data<Config>, config: web::Data<Config>,
) -> Result<HttpResponse, MyError> { ) -> Result<HttpResponse, MyError> {
let nodes = state.node_cache().nodes().await; let nodes = state.node_cache().nodes().await;
let mut buf = BufWriter::new(Vec::new()); let mut buf = BufWriter::new(Vec::new());
crate::templates::index(&mut buf, &nodes, &config)?; crate::templates::index(&mut buf, &nodes, &config)?;

27
src/routes/media.rs Normal file
View file

@ -0,0 +1,27 @@
use crate::{data::Media, error::MyError, requests::Requests};
use actix_web::{web, HttpResponse};
use uuid::Uuid;
pub async fn route(
media: web::Data<Media>,
requests: web::Data<Requests>,
uuid: web::Path<Uuid>,
) -> Result<HttpResponse, MyError> {
let uuid = uuid.into_inner();
if let Some((content_type, bytes)) = media.get_bytes(uuid).await {
return Ok(HttpResponse::Ok().content_type(content_type).body(bytes));
}
if let Some(url) = media.get_url(uuid).await {
let (content_type, bytes) = requests.fetch_bytes(url.as_str()).await?;
media
.store_bytes(uuid, content_type.clone(), bytes.clone())
.await;
return Ok(HttpResponse::Ok().content_type(content_type).body(bytes));
}
Ok(HttpResponse::NotFound().finish())
}

View file

@ -1,6 +1,7 @@
mod actor; mod actor;
mod inbox; mod inbox;
mod index; mod index;
mod media;
mod nodeinfo; mod nodeinfo;
mod statics; mod statics;
@ -8,6 +9,7 @@ pub use self::{
actor::route as actor, actor::route as actor,
inbox::route as inbox, inbox::route as inbox,
index::route as index, index::route as index,
media::route as media,
nodeinfo::{route as nodeinfo, well_known as nodeinfo_meta}, nodeinfo::{route as nodeinfo, well_known as nodeinfo_meta},
statics::route as statics, statics::route as statics,
}; };

15
templates/admin.rs.html Normal file
View file

@ -0,0 +1,15 @@
@use crate::data::Contact;
@(contact: &Contact)
<div class="admin">
<div class="left">
<figure class="avatar">
<img src="@contact.avatar" alt="@contact.display_name's avatar">
</figure>
</div>
<div class="right">
<p class="display-name"><a href="@contact.url">@contact.display_name</a></p>
<p class="username">@@@contact.username</p>
</div>
</div>

View file

@ -1,4 +1,8 @@
@use crate::{config::{Config, UrlKind}, templates::statics::index_css, data::Node}; @use crate::{
config::{Config, UrlKind},
data::Node,
templates::{info, instance, statics::index_css},
};
@(nodes: &[Node], config: &Config) @(nodes: &[Node], config: &Config)
@ -11,28 +15,29 @@
</head> </head>
<body> <body>
<header> <header>
<h1>Welcome to @config.software_name() on @config.hostname()</h1> <div class="header-text">
<h1>@config.software_name()</h1>
<p>on @config.hostname()</p>
</div>
</header> </header>
<main> <main>
<section> <section>
<h3>Connected Servers:</h3> <h3>Connected Servers</h3>
@if nodes.is_empty() { @if nodes.is_empty() {
<p>There are no connected servers at this time.</p> <p>There are no connected servers at this time.</p>
} else { } else {
<ul> <ul>
@for node in nodes { @for node in nodes {
@if let Some(domain) = node.base.as_url().domain() { @if let Some(inst) = node.instance.as_ref() {
<li> <li>
<p><a href="@node.base">@domain</a></p> @:instance(inst, node.info.as_ref().map(|info| { info.software.as_ref() }), node.contact.as_ref(), &node.base)
@if let Some(info) = node.info.as_ref() {
<p>Running @info.software version @info.version</p>
@if info.reg {
<p>Registration is open</p>
} else {
<p>Registration is closed</p>
}
}
</li> </li>
} else {
@if let Some(inf) = node.info.as_ref() {
<li>
@:info(&inf, &node.base)
</li>
}
} }
} }
</ul> </ul>
@ -40,6 +45,7 @@
</section> </section>
<section> <section>
<h3>Joining</h3> <h3>Joining</h3>
<article class="joining">
<p> <p>
If you are the admin of a server that supports activitypub relays, you can add If you are the admin of a server that supports activitypub relays, you can add
this relay to your server. this relay to your server.
@ -61,6 +67,7 @@
Consult the documentation for your server. It's likely that it follows either Consult the documentation for your server. It's likely that it follows either
Mastodon or Pleroma's relay formatting. Mastodon or Pleroma's relay formatting.
</p> </p>
</article>
</section> </section>
</main> </main>
<footer> <footer>

16
templates/info.rs.html Normal file
View file

@ -0,0 +1,16 @@
@use crate::data::Info;
@use activitystreams::primitives::XsdAnyUri;
@(info: &Info, base: &XsdAnyUri)
<article class="info">
@if let Some(domain) = base.as_url().domain() {
<h4 class="padded"><a href="@base">@domain</a></h4>
}
<p class="padded">
Running @info.software, version @info.version.
@if info.reg {
Registration is open
}
</p>
</article>

View file

@ -0,0 +1,32 @@
@use crate::{data::{Contact, Instance}, templates::admin};
@use activitystreams::primitives::XsdAnyUri;
@(instance: &Instance, software: Option<&str>, contact: Option<&Contact>, base: &XsdAnyUri)
<article class="instance">
<h4 class="padded"><a href="@base">@instance.title</a></h4>
<p class="padded">
@if let Some(software) = software {
Running @software, version @instance.version.
}
@if instance.reg {
<br>Registration is open.
@if instance.requires_approval {
Accounts must be approved by an admin.
}
} else{
Registration is closed
}
</p>
<div class="instance-info">
<h4 class="instance-description">Description:</h4>
<div class="description">
<div class="please-stay">
@Html(&instance.description)
</div>
</div>
@if let Some(contact) = contact {
@:admin(contact)
}
</div>
</article>