mirror of
https://git.asonix.dog/asonix/relay.git
synced 2024-11-25 11:01:11 +00:00
Add media cache, improve default styles
This commit is contained in:
parent
0a42450801
commit
9ada30626b
18 changed files with 613 additions and 65 deletions
178
Cargo.lock
generated
178
Cargo.lock
generated
|
@ -364,6 +364,21 @@ dependencies = [
|
|||
"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]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
|
@ -930,6 +945,16 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "futures"
|
||||
version = "0.3.4"
|
||||
|
@ -1118,6 +1143,20 @@ dependencies = [
|
|||
"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]]
|
||||
name = "http"
|
||||
version = "0.2.0"
|
||||
|
@ -1339,6 +1378,47 @@ dependencies = [
|
|||
"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]]
|
||||
name = "match_cfg"
|
||||
version = "0.1.0"
|
||||
|
@ -1437,6 +1517,12 @@ dependencies = [
|
|||
"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]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
|
@ -1600,6 +1686,26 @@ dependencies = [
|
|||
"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]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
|
@ -1680,6 +1786,12 @@ version = "0.2.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_env_logger"
|
||||
version = "0.4.0"
|
||||
|
@ -1763,6 +1875,7 @@ dependencies = [
|
|||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
"rand_pcg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1793,6 +1906,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.56"
|
||||
|
@ -1826,12 +1948,14 @@ dependencies = [
|
|||
"actix-rt",
|
||||
"actix-web",
|
||||
"actix-webfinger",
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"background-jobs",
|
||||
"background-jobs-core",
|
||||
"base64 0.12.0",
|
||||
"bb8-postgres",
|
||||
"bytes",
|
||||
"config",
|
||||
"dotenv",
|
||||
"env_logger",
|
||||
|
@ -2246,6 +2370,31 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "stringprep"
|
||||
version = "0.1.2"
|
||||
|
@ -2332,6 +2481,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "termcolor"
|
||||
version = "1.1.0"
|
||||
|
@ -2636,6 +2796,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.1"
|
||||
|
@ -2815,6 +2981,18 @@ dependencies = [
|
|||
"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]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.3"
|
||||
|
|
|
@ -19,9 +19,11 @@ actix-rt = "1.0.0"
|
|||
actix-web = { version = "3.0.0-alpha.1", features = ["rustls"] }
|
||||
actix-webfinger = "0.3.0-alpha.3"
|
||||
activitystreams = "0.5.0-alpha.11"
|
||||
ammonia = "3.1.0"
|
||||
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-core = { version = "0.7.0", git = "https://git.asonix.dog/Aardwolf/background-jobs" }
|
||||
bytes = "0.5.4"
|
||||
base64 = "0.12"
|
||||
bb8-postgres = { version = "0.4.0", features = ["with-serde_json-1", "with-uuid-0_8", "with-chrono-0_4"] }
|
||||
config = "0.10.1"
|
||||
|
|
134
scss/index.scss
134
scss/index.scss
|
@ -7,32 +7,69 @@ body {
|
|||
padding-bottom: 96px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
body,
|
||||
body * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 32px 0;
|
||||
background-color: #333;
|
||||
color: #f5f5f5;
|
||||
text-align: center;
|
||||
|
||||
.header-text {
|
||||
max-width: 700px;
|
||||
margin: auto;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
margin-top: 8px;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e5e5e5;
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 3px;
|
||||
margin: 32px auto 0;
|
||||
max-width: 700px;
|
||||
padding-bottom: 32px;
|
||||
|
||||
> p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
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 {
|
||||
background-color: #333;
|
||||
color: #f5f5f5;
|
||||
|
@ -67,16 +113,78 @@ footer {
|
|||
right: 0;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
&,
|
||||
&:focus,
|
||||
&:hover,
|
||||
&:active {
|
||||
color: #ea7fbc;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ pub enum UrlKind {
|
|||
Inbox,
|
||||
Index,
|
||||
MainKey,
|
||||
Media(Uuid),
|
||||
NodeInfo,
|
||||
Outbox,
|
||||
}
|
||||
|
@ -136,6 +137,7 @@ impl Config {
|
|||
UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname),
|
||||
UrlKind::Index => format!("{}://{}/", 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::Outbox => format!("{}://{}/outbox", scheme, self.hostname),
|
||||
}
|
||||
|
|
60
src/data/media.rs
Normal file
60
src/data/media.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
mod actor;
|
||||
mod media;
|
||||
mod node;
|
||||
mod state;
|
||||
|
||||
pub use self::{
|
||||
actor::{Actor, ActorCache},
|
||||
node::{Node, NodeCache},
|
||||
media::Media,
|
||||
node::{Contact, Info, Instance, Node, NodeCache},
|
||||
state::State,
|
||||
};
|
||||
|
|
|
@ -70,6 +70,9 @@ pub enum MyError {
|
|||
#[error("Hosts don't match, {0}, {1}")]
|
||||
HostMismatch(String, String),
|
||||
|
||||
#[error("Invalid or missing content type")]
|
||||
ContentType,
|
||||
|
||||
#[error("Couldn't flush buffer")]
|
||||
FlushBuffer,
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::jobs::JobState;
|
||||
use crate::{config::UrlKind, jobs::JobState};
|
||||
use activitystreams::primitives::XsdAnyUri;
|
||||
use anyhow::Error;
|
||||
use background_jobs::{Job, Processor};
|
||||
|
@ -44,7 +44,14 @@ impl QueryInstance {
|
|||
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
|
||||
.node_cache
|
||||
.set_contact(
|
||||
|
@ -57,6 +64,8 @@ impl QueryInstance {
|
|||
.await?;
|
||||
}
|
||||
|
||||
let description = ammonia::clean(&description);
|
||||
|
||||
state
|
||||
.node_cache
|
||||
.set_instance(
|
||||
|
|
|
@ -9,7 +9,8 @@ pub use self::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
data::{ActorCache, NodeCache, State},
|
||||
config::Config,
|
||||
data::{ActorCache, Media, NodeCache, State},
|
||||
db::Db,
|
||||
error::MyError,
|
||||
jobs::{
|
||||
|
@ -33,17 +34,31 @@ pub fn create_server(db: Db) -> JobServer {
|
|||
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();
|
||||
|
||||
WorkerConfig::new(move || JobState::new(state.clone(), actors.clone(), job_server.clone()))
|
||||
.register(DeliverProcessor)
|
||||
.register(DeliverManyProcessor)
|
||||
.register(NodeinfoProcessor)
|
||||
.register(InstanceProcessor)
|
||||
.register(ListenersProcessor)
|
||||
.set_processor_count("default", 4)
|
||||
.start(remote_handle);
|
||||
WorkerConfig::new(move || {
|
||||
JobState::new(
|
||||
state.clone(),
|
||||
actors.clone(),
|
||||
job_server.clone(),
|
||||
media.clone(),
|
||||
config.clone(),
|
||||
)
|
||||
})
|
||||
.register(DeliverProcessor)
|
||||
.register(DeliverManyProcessor)
|
||||
.register(NodeinfoProcessor)
|
||||
.register(InstanceProcessor)
|
||||
.register(ListenersProcessor)
|
||||
.set_processor_count("default", 4)
|
||||
.start(remote_handle);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -51,6 +66,8 @@ pub struct JobState {
|
|||
requests: Requests,
|
||||
state: State,
|
||||
actors: ActorCache,
|
||||
config: Config,
|
||||
media: Media,
|
||||
node_cache: NodeCache,
|
||||
job_server: JobServer,
|
||||
}
|
||||
|
@ -61,11 +78,19 @@ pub struct JobServer {
|
|||
}
|
||||
|
||||
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 {
|
||||
requests: state.requests(),
|
||||
node_cache: state.node_cache(),
|
||||
actors,
|
||||
config,
|
||||
media,
|
||||
state,
|
||||
job_server,
|
||||
}
|
||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -16,7 +16,7 @@ mod routes;
|
|||
use self::{
|
||||
args::Args,
|
||||
config::Config,
|
||||
data::{ActorCache, State},
|
||||
data::{ActorCache, Media, State},
|
||||
db::Db,
|
||||
jobs::{create_server, create_workers},
|
||||
middleware::RelayResolver,
|
||||
|
@ -62,6 +62,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let media = Media::new();
|
||||
let state = State::hydrate(config.clone(), &db).await?;
|
||||
let actors = ActorCache::new(db.clone());
|
||||
let job_server = create_server(db.clone());
|
||||
|
@ -84,9 +85,11 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
let state = state.clone();
|
||||
let actors = actors.clone();
|
||||
let job_server = job_server.clone();
|
||||
let media = media.clone();
|
||||
let config = config.clone();
|
||||
|
||||
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?;
|
||||
|
@ -98,7 +101,13 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
let bind_address = config.bind_address();
|
||||
HttpServer::new(move || {
|
||||
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()
|
||||
|
@ -109,7 +118,9 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
.data(actors.clone())
|
||||
.data(config.clone())
|
||||
.data(job_server.clone())
|
||||
.data(media.clone())
|
||||
.service(web::resource("/").route(web::get().to(index)))
|
||||
.service(web::resource("/media/{path}").route(web::get().to(routes::media)))
|
||||
.service(
|
||||
web::resource("/inbox")
|
||||
.wrap(config.digest_middleware())
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::error::MyError;
|
||||
use activitystreams::primitives::XsdAnyUri;
|
||||
use actix_web::client::Client;
|
||||
use bytes::Bytes;
|
||||
use http_signature_normalization_actix::prelude::*;
|
||||
use log::error;
|
||||
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>
|
||||
where
|
||||
T: serde::ser::Serialize,
|
||||
|
|
|
@ -8,7 +8,6 @@ pub async fn route(
|
|||
config: web::Data<Config>,
|
||||
) -> Result<HttpResponse, MyError> {
|
||||
let nodes = state.node_cache().nodes().await;
|
||||
|
||||
let mut buf = BufWriter::new(Vec::new());
|
||||
|
||||
crate::templates::index(&mut buf, &nodes, &config)?;
|
||||
|
|
27
src/routes/media.rs
Normal file
27
src/routes/media.rs
Normal 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())
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
mod actor;
|
||||
mod inbox;
|
||||
mod index;
|
||||
mod media;
|
||||
mod nodeinfo;
|
||||
mod statics;
|
||||
|
||||
|
@ -8,6 +9,7 @@ pub use self::{
|
|||
actor::route as actor,
|
||||
inbox::route as inbox,
|
||||
index::route as index,
|
||||
media::route as media,
|
||||
nodeinfo::{route as nodeinfo, well_known as nodeinfo_meta},
|
||||
statics::route as statics,
|
||||
};
|
||||
|
|
15
templates/admin.rs.html
Normal file
15
templates/admin.rs.html
Normal 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>
|
|
@ -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)
|
||||
|
||||
|
@ -11,28 +15,29 @@
|
|||
</head>
|
||||
<body>
|
||||
<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>
|
||||
<main>
|
||||
<section>
|
||||
<h3>Connected Servers:</h3>
|
||||
<h3>Connected Servers</h3>
|
||||
@if nodes.is_empty() {
|
||||
<p>There are no connected servers at this time.</p>
|
||||
} else {
|
||||
<ul>
|
||||
@for node in nodes {
|
||||
@if let Some(domain) = node.base.as_url().domain() {
|
||||
@if let Some(inst) = node.instance.as_ref() {
|
||||
<li>
|
||||
<p><a href="@node.base">@domain</a></p>
|
||||
@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>
|
||||
}
|
||||
}
|
||||
@:instance(inst, node.info.as_ref().map(|info| { info.software.as_ref() }), node.contact.as_ref(), &node.base)
|
||||
</li>
|
||||
} else {
|
||||
@if let Some(inf) = node.info.as_ref() {
|
||||
<li>
|
||||
@:info(&inf, &node.base)
|
||||
</li>
|
||||
}
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
|
@ -40,27 +45,29 @@
|
|||
</section>
|
||||
<section>
|
||||
<h3>Joining</h3>
|
||||
<p>
|
||||
If you are the admin of a server that supports activitypub relays, you can add
|
||||
this relay to your server.
|
||||
</p>
|
||||
<h4>Mastodon</h4>
|
||||
<p>
|
||||
Mastodon admins can add this relay by adding
|
||||
<pre>@config.generate_url(UrlKind::Inbox)</pre> in their relay settings.
|
||||
</p>
|
||||
<h4>Pleroma</h4>
|
||||
<p>
|
||||
Pleroma admins can add this relay by adding
|
||||
<pre>@config.generate_url(UrlKind::Actor)</pre>
|
||||
to their relay settings (I don't actually know how pleroma handles adding
|
||||
relays, is it still a mix command?).
|
||||
</p>
|
||||
<h4>Others</h4>
|
||||
<p>
|
||||
Consult the documentation for your server. It's likely that it follows either
|
||||
Mastodon or Pleroma's relay formatting.
|
||||
</p>
|
||||
<article class="joining">
|
||||
<p>
|
||||
If you are the admin of a server that supports activitypub relays, you can add
|
||||
this relay to your server.
|
||||
</p>
|
||||
<h4>Mastodon</h4>
|
||||
<p>
|
||||
Mastodon admins can add this relay by adding
|
||||
<pre>@config.generate_url(UrlKind::Inbox)</pre> in their relay settings.
|
||||
</p>
|
||||
<h4>Pleroma</h4>
|
||||
<p>
|
||||
Pleroma admins can add this relay by adding
|
||||
<pre>@config.generate_url(UrlKind::Actor)</pre>
|
||||
to their relay settings (I don't actually know how pleroma handles adding
|
||||
relays, is it still a mix command?).
|
||||
</p>
|
||||
<h4>Others</h4>
|
||||
<p>
|
||||
Consult the documentation for your server. It's likely that it follows either
|
||||
Mastodon or Pleroma's relay formatting.
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
<footer>
|
||||
|
|
16
templates/info.rs.html
Normal file
16
templates/info.rs.html
Normal 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>
|
32
templates/instance.rs.html
Normal file
32
templates/instance.rs.html
Normal 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>
|
Loading…
Reference in a new issue