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…
	
	Add table
		
		Reference in a new issue