Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b596e77f03 | ||
|
41f97b01f0 | ||
|
a508a4150c | ||
|
25c40adf20 | ||
|
7490567a21 | ||
|
492bbb1ba6 | ||
|
cf3708e1c6 | ||
|
df442002c2 | ||
|
07036b5fad | ||
|
0726375859 | ||
|
cb1c260692 | ||
|
de6bfca084 | ||
|
7aabb9661e | ||
|
18bb413011 | ||
|
d2881ee3f7 | ||
|
850b3c1337 | ||
|
44ebce516c | ||
|
3c830ab0ce | ||
|
097d0ea9ce | ||
|
6fe16c9f84 | ||
|
43cb9f700c | ||
|
2c285b9aca | ||
|
e4bb73d22e | ||
|
e9c7259ffb | ||
|
be8c67ee9a | ||
|
65b2c38c29 | ||
|
8aa99cea35 | ||
|
a010025074 | ||
|
82088596a8 | ||
|
87ce3a7b51 | ||
|
3472a58299 | ||
|
a3f165f9f4 | ||
|
25c5da1a7c | ||
|
022e037eea | ||
|
45c335e17b | ||
|
b51551973a | ||
|
59e5c49aa8 | ||
|
ce119ffe50 | ||
|
944f8c42fa | ||
|
909f677bdd | ||
|
fd9764ff17 | ||
|
75722abc9e | ||
|
ec9b699c6e | ||
|
bb5c2b69a7 | ||
|
e52944e477 | ||
|
928470610e |
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,3 +18,4 @@ tags.*
|
|||||||
search_index
|
search_index
|
||||||
.buildconfig
|
.buildconfig
|
||||||
__pycache__
|
__pycache__
|
||||||
|
.vscode/
|
||||||
|
3685
Cargo.lock
generated
3685
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@ -8,6 +8,7 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
activitypub = "0.1.3"
|
activitypub = "0.1.3"
|
||||||
askama_escape = "0.1"
|
askama_escape = "0.1"
|
||||||
|
async-trait = "*"
|
||||||
atom_syndication = "0.6"
|
atom_syndication = "0.6"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
colored = "1.8"
|
colored = "1.8"
|
||||||
@ -20,20 +21,19 @@ heck = "0.3.0"
|
|||||||
lettre = "0.9.2"
|
lettre = "0.9.2"
|
||||||
lettre_email = "0.9.2"
|
lettre_email = "0.9.2"
|
||||||
num_cpus = "1.10"
|
num_cpus = "1.10"
|
||||||
rocket = "0.4.2"
|
rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" }
|
||||||
rocket_contrib = { version = "0.4.2", features = ["json"] }
|
rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" , features = ["json"] }
|
||||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
|
||||||
rpassword = "4.0"
|
rpassword = "4.0"
|
||||||
runtime-fmt = "0.4.0"
|
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.2"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_qs = "0.5"
|
serde_qs = "0.5"
|
||||||
shrinkwraprs = "0.2.1"
|
shrinkwraprs = "0.2.1"
|
||||||
syntect = "3.3"
|
syntect = "3.3"
|
||||||
validator = "0.8"
|
tokio = "0.2"
|
||||||
validator_derive = "0.8"
|
validator = "0.10"
|
||||||
webfinger = "0.4.1"
|
validator_derive = "0.10"
|
||||||
|
webfinger = { git = "https://github.com/Plume-org/webfinger", rev = "4e8f12810c4a7ba7a07bbcb722cd265fdff512b6", features = ["async"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "plume"
|
name = "plume"
|
||||||
@ -65,9 +65,11 @@ path = "plume-common"
|
|||||||
[dependencies.plume-models]
|
[dependencies.plume-models]
|
||||||
path = "plume-models"
|
path = "plume-models"
|
||||||
|
|
||||||
[dependencies.rocket_csrf]
|
[dependencies.rocket_i18n]
|
||||||
git = "https://github.com/fdb-hiroshima/rocket_csrf"
|
git = "https://github.com/Plume-org/rocket_i18n"
|
||||||
rev = "29910f2829e7e590a540da3804336577b48c7b31"
|
branch = "go-async"
|
||||||
|
default-features = false
|
||||||
|
features = ["rocket"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
ructe = "0.9.0"
|
ructe = "0.9.0"
|
||||||
|
2
build.rs
2
build.rs
@ -1,5 +1,3 @@
|
|||||||
use rsass;
|
|
||||||
|
|
||||||
use ructe::Ructe;
|
use ructe::Ructe;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::{ffi::OsStr, fs::*, io::Write, path::*};
|
use std::{ffi::OsStr, fs::*, io::Write, path::*};
|
||||||
|
@ -6,22 +6,22 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
activitypub = "0.1.1"
|
activitypub = "0.1.1"
|
||||||
activitystreams-derive = "0.1.1"
|
activitystreams-derive = "0.2"
|
||||||
activitystreams-traits = "0.1.0"
|
activitystreams-traits = "0.1.0"
|
||||||
array_tool = "1.0"
|
array_tool = "1.0"
|
||||||
base64 = "0.10"
|
base64 = "0.10"
|
||||||
|
futures-util = "*"
|
||||||
heck = "0.3.0"
|
heck = "0.3.0"
|
||||||
hex = "0.3"
|
hex = "0.3"
|
||||||
hyper = "0.12.33"
|
hyper = "0.13"
|
||||||
openssl = "0.10.22"
|
openssl = "0.10.22"
|
||||||
rocket = "0.4.0"
|
rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" }
|
||||||
reqwest = "0.9"
|
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
shrinkwraprs = "0.2.1"
|
shrinkwraprs = "0.2.1"
|
||||||
syntect = "3.3"
|
syntect = "3.3"
|
||||||
tokio = "0.1.22"
|
tokio = "0.2"
|
||||||
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] }
|
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] }
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
@ -31,3 +31,7 @@ version = "0.4"
|
|||||||
[dependencies.pulldown-cmark]
|
[dependencies.pulldown-cmark]
|
||||||
default-features = false
|
default-features = false
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
||||||
|
[dependencies.reqwest]
|
||||||
|
features = ["json", "blocking"]
|
||||||
|
version = "0.10"
|
||||||
|
@ -279,8 +279,9 @@ pub trait FromId<C>: Sized {
|
|||||||
|
|
||||||
/// Dereferences an ID
|
/// Dereferences an ID
|
||||||
fn deref(id: &str) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
fn deref(id: &str) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
||||||
reqwest::ClientBuilder::new()
|
// Use blocking reqwest API here, since defer cannot be async (yet)
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
reqwest::blocking::Client::builder()
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(5))
|
||||||
.build()
|
.build()
|
||||||
.map_err(|_| (None, InboxError::DerefError.into()))?
|
.map_err(|_| (None, InboxError::DerefError.into()))?
|
||||||
.get(id)
|
.get(id)
|
||||||
@ -296,7 +297,7 @@ pub trait FromId<C>: Sized {
|
|||||||
)
|
)
|
||||||
.send()
|
.send()
|
||||||
.map_err(|_| (None, InboxError::DerefError))
|
.map_err(|_| (None, InboxError::DerefError))
|
||||||
.and_then(|mut r| {
|
.and_then(|r| {
|
||||||
let json: serde_json::Value = r
|
let json: serde_json::Value = r
|
||||||
.json()
|
.json()
|
||||||
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
|
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
use activitypub::{Activity, Link, Object};
|
use activitypub::{Activity, Link, Object};
|
||||||
use array_tool::vec::Uniq;
|
use array_tool::vec::Uniq;
|
||||||
use reqwest::r#async::ClientBuilder;
|
use reqwest::ClientBuilder;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
http::Status,
|
http::Status,
|
||||||
request::{FromRequest, Request},
|
request::{FromRequest, Request},
|
||||||
response::{Responder, Response},
|
response::{Responder, Response, Result},
|
||||||
Outcome,
|
Outcome,
|
||||||
};
|
};
|
||||||
use serde_json;
|
|
||||||
use tokio::prelude::*;
|
|
||||||
|
|
||||||
use self::sign::Signable;
|
use self::sign::Signable;
|
||||||
|
|
||||||
@ -62,39 +60,45 @@ impl<T> ActivityStream<T> {
|
|||||||
ActivityStream(t)
|
ActivityStream(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'r, O: Object> Responder<'r> for ActivityStream<O> {
|
impl<'r, O: Object + Send + 'r> Responder<'r> for ActivityStream<O> {
|
||||||
fn respond_to(self, request: &Request<'_>) -> Result<Response<'r>, Status> {
|
async fn respond_to(self, request: &'r Request<'_>) -> Result<'r> {
|
||||||
let mut json = serde_json::to_value(&self.0).map_err(|_| Status::InternalServerError)?;
|
let mut json = serde_json::to_value(&self.0).map_err(|_| Status::InternalServerError)?;
|
||||||
json["@context"] = context();
|
json["@context"] = context();
|
||||||
serde_json::to_string(&json).respond_to(request).map(|r| {
|
let result = serde_json::to_string(&json).map_err(rocket::response::Debug);
|
||||||
Response::build_from(r)
|
match result.respond_to(request).await {
|
||||||
|
Ok(r) => Response::build_from(r)
|
||||||
.raw_header("Content-Type", "application/activity+json")
|
.raw_header("Content-Type", "application/activity+json")
|
||||||
.finalize()
|
.ok(),
|
||||||
})
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ApRequest;
|
pub struct ApRequest;
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for ApRequest {
|
impl<'a, 'r> FromRequest<'a, 'r> for ApRequest {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> Outcome<Self, (Status, Self::Error), ()> {
|
async fn from_request(request: &'a Request<'r>) -> Outcome<Self, (Status, Self::Error), ()> {
|
||||||
request
|
request
|
||||||
.headers()
|
.headers()
|
||||||
.get_one("Accept")
|
.get_one("Accept")
|
||||||
.map(|header| {
|
.map(|header| {
|
||||||
header
|
header
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(|ct| match ct.trim() {
|
.map(|ct| {
|
||||||
// bool for Forward: true if found a valid Content-Type for Plume first (HTML), false otherwise
|
match ct.trim() {
|
||||||
"application/ld+json; profile=\"https://w3.org/ns/activitystreams\""
|
// bool for Forward: true if found a valid Content-Type for Plume first (HTML),
|
||||||
| "application/ld+json;profile=\"https://w3.org/ns/activitystreams\""
|
// false otherwise
|
||||||
| "application/activity+json"
|
"application/ld+json; profile=\"https://w3.org/ns/activitystreams\""
|
||||||
| "application/ld+json" => Outcome::Success(ApRequest),
|
| "application/ld+json;profile=\"https://w3.org/ns/activitystreams\""
|
||||||
"text/html" => Outcome::Forward(true),
|
| "application/activity+json"
|
||||||
_ => Outcome::Forward(false),
|
| "application/ld+json" => Outcome::Success(ApRequest),
|
||||||
|
"text/html" => Outcome::Forward(true),
|
||||||
|
_ => Outcome::Forward(false),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.fold(Outcome::Forward(false), |out, ct| {
|
.fold(Outcome::Forward(false), |out, ct| {
|
||||||
if out.clone().forwarded().unwrap_or_else(|| out.is_success()) {
|
if out.clone().forwarded().unwrap_or_else(|| out.is_success()) {
|
||||||
@ -130,36 +134,38 @@ where
|
|||||||
.sign(sender)
|
.sign(sender)
|
||||||
.expect("activity_pub::broadcast: signature error");
|
.expect("activity_pub::broadcast: signature error");
|
||||||
|
|
||||||
let mut rt = tokio::runtime::current_thread::Runtime::new()
|
let rt = tokio::runtime::Builder::new()
|
||||||
.expect("Error while initializing tokio runtime for federation");
|
.threaded_scheduler()
|
||||||
let client = ClientBuilder::new()
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(5))
|
|
||||||
.build()
|
.build()
|
||||||
.expect("Can't build client");
|
.expect("Error while initializing tokio runtime for federation");
|
||||||
for inbox in boxes {
|
for inbox in boxes {
|
||||||
let body = signed.to_string();
|
let body = signed.to_string();
|
||||||
let mut headers = request::headers();
|
let mut headers = request::headers();
|
||||||
headers.insert("Digest", request::Digest::digest(&body));
|
headers.insert("Digest", request::Digest::digest(&body));
|
||||||
rt.spawn(
|
let sig = request::signature(sender, &headers)
|
||||||
|
.expect("activity_pub::broadcast: request signature error");
|
||||||
|
let client = ClientBuilder::new()
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(5))
|
||||||
|
.build()
|
||||||
|
.expect("Can't build client");
|
||||||
|
rt.spawn(async move {
|
||||||
client
|
client
|
||||||
.post(&inbox)
|
.post(&inbox)
|
||||||
.headers(headers.clone())
|
.headers(headers.clone())
|
||||||
.header(
|
.header("Signature", sig)
|
||||||
"Signature",
|
|
||||||
request::signature(sender, &headers)
|
|
||||||
.expect("activity_pub::broadcast: request signature error"),
|
|
||||||
)
|
|
||||||
.body(body)
|
.body(body)
|
||||||
.send()
|
.send()
|
||||||
.and_then(|r| r.into_body().concat2())
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
.map(move |response| {
|
.map(move |response| {
|
||||||
println!("Successfully sent activity to inbox ({})", inbox);
|
println!("Successfully sent activity to inbox ({})", inbox);
|
||||||
println!("Response: \"{:?}\"\n", response)
|
println!("Response: \"{:?}\"\n", response)
|
||||||
})
|
})
|
||||||
.map_err(|e| println!("Error while sending to inbox ({:?})", e)),
|
.map_err(|e| println!("Error while sending to inbox ({:?})", e))
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
rt.run().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Shrinkwrap, Clone, Serialize, Deserialize)]
|
#[derive(Shrinkwrap, Clone, Serialize, Deserialize)]
|
||||||
@ -203,8 +209,7 @@ pub struct PublicKey {
|
|||||||
pub public_key_pem: Option<serde_json::Value>,
|
pub public_key_pem: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, UnitString)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
#[activitystreams(Hashtag)]
|
|
||||||
pub struct HashtagType;
|
pub struct HashtagType;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)]
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use base64;
|
|
||||||
use chrono::{offset::Utc, DateTime};
|
use chrono::{offset::Utc, DateTime};
|
||||||
use openssl::hash::{Hasher, MessageDigest};
|
use openssl::hash::{Hasher, MessageDigest};
|
||||||
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT};
|
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT};
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
use super::request;
|
use super::request;
|
||||||
use base64;
|
|
||||||
use chrono::{naive::NaiveDateTime, DateTime, Duration, Utc};
|
use chrono::{naive::NaiveDateTime, DateTime, Duration, Utc};
|
||||||
use hex;
|
|
||||||
use openssl::{pkey::PKey, rsa::Rsa, sha::sha256};
|
use openssl::{pkey::PKey, rsa::Rsa, sha::sha256};
|
||||||
use rocket::http::HeaderMap;
|
use rocket::http::HeaderMap;
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
/// Returns (public key, private key)
|
/// Returns (public key, private key)
|
||||||
pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
|
pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate activitystreams_derive;
|
extern crate activitystreams_derive;
|
||||||
use activitystreams_traits;
|
|
||||||
|
|
||||||
use serde;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate shrinkwraprs;
|
extern crate shrinkwraprs;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -10,24 +10,24 @@ ammonia = "2.1.1"
|
|||||||
askama_escape = "0.1"
|
askama_escape = "0.1"
|
||||||
bcrypt = "0.5"
|
bcrypt = "0.5"
|
||||||
guid-create = "0.1"
|
guid-create = "0.1"
|
||||||
|
futures = "0.3"
|
||||||
heck = "0.3.0"
|
heck = "0.3.0"
|
||||||
itertools = "0.8.0"
|
itertools = "0.8.0"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
migrations_internals= "1.4.0"
|
migrations_internals= "1.4.0"
|
||||||
openssl = "0.10.22"
|
openssl = "0.10.22"
|
||||||
rocket = "0.4.0"
|
rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" }
|
||||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
|
||||||
reqwest = "0.9"
|
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.2"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tantivy = "0.10.1"
|
tantivy = "0.10.1"
|
||||||
|
tokio = "0.2"
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
walkdir = "2.2"
|
walkdir = "2.2"
|
||||||
webfinger = "0.4.1"
|
webfinger = { git = "https://github.com/Plume-org/webfinger", rev = "4e8f12810c4a7ba7a07bbcb722cd265fdff512b6", features = ["async"] }
|
||||||
whatlang = "0.7.1"
|
whatlang = "0.7.1"
|
||||||
shrinkwraprs = "0.2.1"
|
shrinkwraprs = "0.3"
|
||||||
diesel-derive-newtype = "0.1.2"
|
diesel-derive-newtype = "0.1.2"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
|
|
||||||
@ -48,8 +48,19 @@ path = "../plume-common"
|
|||||||
[dependencies.plume-macro]
|
[dependencies.plume-macro]
|
||||||
path = "../plume-macro"
|
path = "../plume-macro"
|
||||||
|
|
||||||
|
[dependencies.reqwest]
|
||||||
|
features = ["json", "blocking"]
|
||||||
|
version = "0.10"
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies.rocket_i18n]
|
||||||
|
git = "https://github.com/Plume-org/rocket_i18n"
|
||||||
|
branch = "go-async"
|
||||||
|
default-features = false
|
||||||
|
features = ["rocket"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
diesel_migrations = "1.3.0"
|
diesel_migrations = "1.4.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
postgres = ["diesel/postgres", "plume-macro/postgres" ]
|
postgres = ["diesel/postgres", "plume-macro/postgres" ]
|
||||||
|
@ -8,11 +8,12 @@ use rocket::{
|
|||||||
/// Wrapper around User to use as a request guard on pages reserved to admins.
|
/// Wrapper around User to use as a request guard on pages reserved to admins.
|
||||||
pub struct Admin(pub User);
|
pub struct Admin(pub User);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Admin, ()> {
|
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
let user = request.guard::<User>()?;
|
let user = try_outcome!(User::from_request(request).await);
|
||||||
if user.is_admin() {
|
if user.is_admin() {
|
||||||
Outcome::Success(Admin(user))
|
Outcome::Success(Admin(user))
|
||||||
} else {
|
} else {
|
||||||
@ -24,11 +25,12 @@ impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
|||||||
/// Same as `Admin` but for moderators.
|
/// Same as `Admin` but for moderators.
|
||||||
pub struct Moderator(pub User);
|
pub struct Moderator(pub User);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for Moderator {
|
impl<'a, 'r> FromRequest<'a, 'r> for Moderator {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Moderator, ()> {
|
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
let user = request.guard::<User>()?;
|
let user = try_outcome!(User::from_request(request).await);
|
||||||
if user.is_moderator() {
|
if user.is_moderator() {
|
||||||
Outcome::Success(Moderator(user))
|
Outcome::Success(Moderator(user))
|
||||||
} else {
|
} else {
|
||||||
|
@ -76,32 +76,36 @@ pub enum TokenError {
|
|||||||
DbError,
|
DbError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
|
impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
|
||||||
type Error = TokenError;
|
type Error = TokenError;
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<ApiToken, TokenError> {
|
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
let headers: Vec<_> = request.headers().get("Authorization").collect();
|
let headers: Vec<_> = request.headers().get("Authorization").collect();
|
||||||
if headers.len() != 1 {
|
if headers.len() != 1 {
|
||||||
return Outcome::Failure((Status::BadRequest, TokenError::NoHeader));
|
return Outcome::Failure((Status::BadRequest, TokenError::NoHeader));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut parsed_header = headers[0].split(' ');
|
let mut parsed_header = headers[0].split(' ');
|
||||||
let auth_type = parsed_header.next().map_or_else(
|
if let Some(auth_type) = parsed_header.next() {
|
||||||
|| Outcome::Failure((Status::BadRequest, TokenError::NoType)),
|
if let Some(val) = parsed_header.next() {
|
||||||
Outcome::Success,
|
if auth_type == "Bearer" {
|
||||||
)?;
|
if let Outcome::Success(conn) = DbConn::from_request(request).await {
|
||||||
let val = parsed_header.next().map_or_else(
|
if let Ok(token) = ApiToken::find_by_value(&*conn, val) {
|
||||||
|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)),
|
return Outcome::Success(token);
|
||||||
Outcome::Success,
|
}
|
||||||
)?;
|
} else {
|
||||||
|
return Outcome::Failure((
|
||||||
if auth_type == "Bearer" {
|
Status::InternalServerError,
|
||||||
let conn = request
|
TokenError::DbError,
|
||||||
.guard::<DbConn>()
|
));
|
||||||
.map_failure(|_| (Status::InternalServerError, TokenError::DbError))?;
|
}
|
||||||
if let Ok(token) = ApiToken::find_by_value(&*conn, val) {
|
}
|
||||||
return Outcome::Success(token);
|
} else {
|
||||||
|
return Outcome::Failure((Status::BadRequest, TokenError::NoValue));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Outcome::Failure((Status::BadRequest, TokenError::NoType));
|
||||||
}
|
}
|
||||||
|
|
||||||
Outcome::Forward(())
|
Outcome::Forward(())
|
||||||
|
@ -20,7 +20,6 @@ use plume_common::activity_pub::{
|
|||||||
inbox::{AsActor, FromId},
|
inbox::{AsActor, FromId},
|
||||||
sign, ActivityStream, ApSignature, Id, IntoId, PublicKey, Source,
|
sign, ActivityStream, ApSignature, Id, IntoId, PublicKey, Source,
|
||||||
};
|
};
|
||||||
use serde_json;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use webfinger::*;
|
use webfinger::*;
|
||||||
|
|
||||||
@ -71,7 +70,8 @@ impl Blog {
|
|||||||
insert!(blogs, NewBlog, |inserted, conn| {
|
insert!(blogs, NewBlog, |inserted, conn| {
|
||||||
let instance = inserted.get_instance(conn)?;
|
let instance = inserted.get_instance(conn)?;
|
||||||
if inserted.outbox_url.is_empty() {
|
if inserted.outbox_url.is_empty() {
|
||||||
inserted.outbox_url = instance.compute_box(BLOG_PREFIX, &inserted.actor_id, "outbox");
|
inserted.outbox_url =
|
||||||
|
instance.compute_box(BLOG_PREFIX, &inserted.actor_id, r#"outbox"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
if inserted.inbox_url.is_empty() {
|
if inserted.inbox_url.is_empty() {
|
||||||
@ -132,7 +132,7 @@ impl Blog {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_fqn(c: &PlumeRocket, fqn: &str) -> Result<Blog> {
|
pub async fn find_by_fqn(c: &PlumeRocket, fqn: &str) -> Result<Blog> {
|
||||||
let from_db = blogs::table
|
let from_db = blogs::table
|
||||||
.filter(blogs::fqn.eq(fqn))
|
.filter(blogs::fqn.eq(fqn))
|
||||||
.first(&*c.conn)
|
.first(&*c.conn)
|
||||||
@ -140,12 +140,13 @@ impl Blog {
|
|||||||
if let Some(from_db) = from_db {
|
if let Some(from_db) = from_db {
|
||||||
Ok(from_db)
|
Ok(from_db)
|
||||||
} else {
|
} else {
|
||||||
Blog::fetch_from_webfinger(c, fqn)
|
Blog::fetch_from_webfinger(c, fqn).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_from_webfinger(c: &PlumeRocket, acct: &str) -> Result<Blog> {
|
async fn fetch_from_webfinger(c: &PlumeRocket, acct: &str) -> Result<Blog> {
|
||||||
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)?
|
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)
|
||||||
|
.await?
|
||||||
.links
|
.links
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||||
|
@ -17,6 +17,7 @@ use activitypub::{
|
|||||||
};
|
};
|
||||||
use chrono::{self, NaiveDateTime};
|
use chrono::{self, NaiveDateTime};
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
||||||
|
use futures::stream::{self, StreamExt};
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
@ -24,7 +25,6 @@ use plume_common::{
|
|||||||
},
|
},
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
use serde_json;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, Clone, AsChangeset)]
|
#[derive(Queryable, Identifiable, Clone, AsChangeset)]
|
||||||
@ -105,7 +105,7 @@ impl Comment {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_activity(&self, c: &PlumeRocket) -> Result<Note> {
|
pub async fn to_activity(&self, c: &PlumeRocket) -> Result<Note> {
|
||||||
let author = User::get(&c.conn, self.author_id)?;
|
let author = User::get(&c.conn, self.author_id)?;
|
||||||
let (html, mentions, _hashtags) = utils::md_to_html(
|
let (html, mentions, _hashtags) = utils::md_to_html(
|
||||||
self.content.get().as_ref(),
|
self.content.get().as_ref(),
|
||||||
@ -132,18 +132,18 @@ impl Comment {
|
|||||||
note.object_props.set_attributed_to_link(author.into_id())?;
|
note.object_props.set_attributed_to_link(author.into_id())?;
|
||||||
note.object_props.set_to_link_vec(to)?;
|
note.object_props.set_to_link_vec(to)?;
|
||||||
note.object_props.set_tag_link_vec(
|
note.object_props.set_tag_link_vec(
|
||||||
mentions
|
stream::iter(mentions)
|
||||||
.into_iter()
|
.filter_map(|m| async move { Mention::build_activity(c, &m).await.ok() })
|
||||||
.filter_map(|m| Mention::build_activity(c, &m).ok())
|
.collect::<Vec<link::Mention>>()
|
||||||
.collect::<Vec<link::Mention>>(),
|
.await,
|
||||||
)?;
|
)?;
|
||||||
Ok(note)
|
Ok(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_activity(&self, c: &PlumeRocket) -> Result<Create> {
|
pub async fn create_activity(&self, c: &PlumeRocket) -> Result<Create> {
|
||||||
let author = User::get(&c.conn, self.author_id)?;
|
let author = User::get(&c.conn, self.author_id)?;
|
||||||
|
|
||||||
let note = self.to_activity(c)?;
|
let note = self.to_activity(c).await?;
|
||||||
let mut act = Create::default();
|
let mut act = Create::default();
|
||||||
act.create_props.set_actor_link(author.into_id())?;
|
act.create_props.set_actor_link(author.into_id())?;
|
||||||
act.create_props.set_object_object(note.clone())?;
|
act.create_props.set_object_object(note.clone())?;
|
||||||
|
@ -7,7 +7,7 @@ use diesel::{dsl::sql_query, ConnectionError, RunQueryDsl};
|
|||||||
use rocket::{
|
use rocket::{
|
||||||
http::Status,
|
http::Status,
|
||||||
request::{self, FromRequest},
|
request::{self, FromRequest},
|
||||||
Outcome, Request, State,
|
Outcome, Request,
|
||||||
};
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
@ -21,14 +21,14 @@ pub struct DbConn(pub PooledConnection<ConnectionManager<Connection>>);
|
|||||||
/// Attempts to retrieve a single connection from the managed database pool. If
|
/// Attempts to retrieve a single connection from the managed database pool. If
|
||||||
/// no pool is currently managed, fails with an `InternalServerError` status. If
|
/// no pool is currently managed, fails with an `InternalServerError` status. If
|
||||||
/// no connections are available, fails with a `ServiceUnavailable` status.
|
/// no connections are available, fails with a `ServiceUnavailable` status.
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
|
impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
let pool = request.guard::<State<'_, DbPool>>()?;
|
match DbConn::from_request(request).await {
|
||||||
match pool.get() {
|
Outcome::Success(a) => Outcome::Success(a),
|
||||||
Ok(conn) => Outcome::Success(DbConn(conn)),
|
_ => Outcome::Failure((Status::ServiceUnavailable, ())),
|
||||||
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,11 @@ use rocket::{
|
|||||||
|
|
||||||
pub struct Headers<'r>(pub HeaderMap<'r>);
|
pub struct Headers<'r>(pub HeaderMap<'r>);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for Headers<'r> {
|
impl<'a, 'r> FromRequest<'a, 'r> for Headers<'r> {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
|
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
for header in request.headers().clone().into_iter() {
|
for header in request.headers().clone().into_iter() {
|
||||||
headers.add(header);
|
headers.add(header);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use activitypub::activity::*;
|
use activitypub::activity::*;
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
comments::Comment,
|
comments::Comment,
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate diesel;
|
extern crate diesel;
|
||||||
|
extern crate futures;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -75,6 +76,12 @@ impl From<std::option::NoneError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Error> for std::option::NoneError {
|
||||||
|
fn from(_: Error) -> Self {
|
||||||
|
std::option::NoneError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<url::ParseError> for Error {
|
impl From<url::ParseError> for Error {
|
||||||
fn from(_: url::ParseError) -> Self {
|
fn from(_: url::ParseError) -> Self {
|
||||||
Error::Url
|
Error::Url
|
||||||
|
@ -7,8 +7,8 @@ use crate::{
|
|||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
/// Represent what a list is supposed to store. Represented in database as an integer
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
/// Represent what a list is supposed to store. Represented in database as an integer
|
||||||
pub enum ListType {
|
pub enum ListType {
|
||||||
User,
|
User,
|
||||||
Blog,
|
Blog,
|
||||||
@ -58,7 +58,11 @@ struct NewList<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! func {
|
macro_rules! func {
|
||||||
(@elem User $id:expr, $value:expr) => {
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
@elem User $id:expr, $value:expr
|
||||||
|
) => {
|
||||||
|
$(#[$outer])*
|
||||||
NewListElem {
|
NewListElem {
|
||||||
list_id: $id,
|
list_id: $id,
|
||||||
user_id: Some(*$value),
|
user_id: Some(*$value),
|
||||||
@ -66,7 +70,11 @@ macro_rules! func {
|
|||||||
word: None,
|
word: None,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(@elem Blog $id:expr, $value:expr) => {
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
@elem Blog $id:expr, $value:expr
|
||||||
|
) => {
|
||||||
|
$(#[$outer])*
|
||||||
NewListElem {
|
NewListElem {
|
||||||
list_id: $id,
|
list_id: $id,
|
||||||
user_id: None,
|
user_id: None,
|
||||||
@ -74,7 +82,11 @@ macro_rules! func {
|
|||||||
word: None,
|
word: None,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(@elem Word $id:expr, $value:expr) => {
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
@elem Word $id:expr, $value:expr
|
||||||
|
) => {
|
||||||
|
$(#[$outer])*
|
||||||
NewListElem {
|
NewListElem {
|
||||||
list_id: $id,
|
list_id: $id,
|
||||||
user_id: None,
|
user_id: None,
|
||||||
@ -82,7 +94,11 @@ macro_rules! func {
|
|||||||
word: Some($value),
|
word: Some($value),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(@elem Prefix $id:expr, $value:expr) => {
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
@elem Prefix $id:expr, $value:expr
|
||||||
|
) => {
|
||||||
|
$(#[$outer])*
|
||||||
NewListElem {
|
NewListElem {
|
||||||
list_id: $id,
|
list_id: $id,
|
||||||
user_id: None,
|
user_id: None,
|
||||||
@ -99,7 +115,11 @@ macro_rules! func {
|
|||||||
(@out_type Word) => { String };
|
(@out_type Word) => { String };
|
||||||
(@out_type Prefix) => { String };
|
(@out_type Prefix) => { String };
|
||||||
|
|
||||||
(add: $fn:ident, $kind:ident) => {
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
add: $fn:ident, $kind:ident
|
||||||
|
) => {
|
||||||
|
$(#[$outer])*
|
||||||
pub fn $fn(&self, conn: &Connection, vals: &[func!(@in_type $kind)]) -> Result<()> {
|
pub fn $fn(&self, conn: &Connection, vals: &[func!(@in_type $kind)]) -> Result<()> {
|
||||||
if self.kind() != ListType::$kind {
|
if self.kind() != ListType::$kind {
|
||||||
return Err(Error::InvalidValue);
|
return Err(Error::InvalidValue);
|
||||||
@ -116,7 +136,11 @@ macro_rules! func {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(list: $fn:ident, $kind:ident, $table:ident) => {
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
list: $fn:ident, $kind:ident, $table:ident
|
||||||
|
) => {
|
||||||
|
$(#[$outer])*
|
||||||
pub fn $fn(&self, conn: &Connection) -> Result<Vec<func!(@out_type $kind)>> {
|
pub fn $fn(&self, conn: &Connection) -> Result<Vec<func!(@out_type $kind)>> {
|
||||||
if self.kind() != ListType::$kind {
|
if self.kind() != ListType::$kind {
|
||||||
return Err(Error::InvalidValue);
|
return Err(Error::InvalidValue);
|
||||||
@ -132,7 +156,11 @@ macro_rules! func {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
(set: $fn:ident, $kind:ident, $add:ident) => {
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
set: $fn:ident, $kind:ident, $add:ident
|
||||||
|
) => {
|
||||||
|
$(#[$outer])*
|
||||||
pub fn $fn(&self, conn: &Connection, val: &[func!(@in_type $kind)]) -> Result<()> {
|
pub fn $fn(&self, conn: &Connection, val: &[func!(@in_type $kind)]) -> Result<()> {
|
||||||
if self.kind() != ListType::$kind {
|
if self.kind() != ListType::$kind {
|
||||||
return Err(Error::InvalidValue);
|
return Err(Error::InvalidValue);
|
||||||
@ -246,23 +274,35 @@ impl List {
|
|||||||
private::ListElem::prefix_in_list(conn, self, word)
|
private::ListElem::prefix_in_list(conn, self, word)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert new users in a list
|
func! {
|
||||||
func! {add: add_users, User}
|
/// Insert new users in a list
|
||||||
|
add: add_users, User
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert new blogs in a list
|
func! {
|
||||||
func! {add: add_blogs, Blog}
|
/// Insert new blogs in a list
|
||||||
|
add: add_blogs, Blog
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert new words in a list
|
func! {
|
||||||
func! {add: add_words, Word}
|
/// Insert new words in a list
|
||||||
|
add: add_words, Word
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert new prefixes in a list
|
func! {
|
||||||
func! {add: add_prefixes, Prefix}
|
/// Insert new prefixes in a list
|
||||||
|
add: add_prefixes, Prefix
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all users in the list
|
func! {
|
||||||
func! {list: list_users, User, users}
|
/// Get all users in the list
|
||||||
|
list: list_users, User, users
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all blogs in the list
|
func! {
|
||||||
func! {list: list_blogs, Blog, blogs}
|
/// Get all blogs in the list
|
||||||
|
list: list_blogs, Blog, blogs
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all words in the list
|
/// Get all words in the list
|
||||||
pub fn list_words(&self, conn: &Connection) -> Result<Vec<String>> {
|
pub fn list_words(&self, conn: &Connection) -> Result<Vec<String>> {
|
||||||
|
@ -10,8 +10,8 @@ use plume_common::{
|
|||||||
activity_pub::{inbox::FromId, Id},
|
activity_pub::{inbox::FromId, Id},
|
||||||
utils::MediaProcessor,
|
utils::MediaProcessor,
|
||||||
};
|
};
|
||||||
use reqwest;
|
|
||||||
use std::{fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
|
use tokio::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, Identifiable, Queryable)]
|
#[derive(Clone, Identifiable, Queryable)]
|
||||||
pub struct Media {
|
pub struct Media {
|
||||||
@ -197,7 +197,7 @@ impl Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge with save_remote?
|
// TODO: merge with save_remote?
|
||||||
pub fn from_activity(c: &PlumeRocket, image: &Image) -> Result<Media> {
|
pub async fn from_activity(c: &PlumeRocket, image: &Image) -> Result<Media> {
|
||||||
let conn = &*c.conn;
|
let conn = &*c.conn;
|
||||||
let remote_url = image.object_props.url_string().ok()?;
|
let remote_url = image.object_props.url_string().ok()?;
|
||||||
let ext = remote_url
|
let ext = remote_url
|
||||||
@ -211,11 +211,9 @@ impl Media {
|
|||||||
ext
|
ext
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut dest = fs::File::create(path.clone()).ok()?;
|
let mut dest = tokio::fs::File::create(path.clone()).await?;
|
||||||
reqwest::get(remote_url.as_str())
|
let contents = reqwest::get(remote_url.as_str()).await?.bytes().await?;
|
||||||
.ok()?
|
dest.write_all(&contents).await?;
|
||||||
.copy_to(&mut dest)
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
Media::insert(
|
Media::insert(
|
||||||
conn,
|
conn,
|
||||||
|
@ -52,8 +52,8 @@ impl Mention {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_activity(c: &PlumeRocket, ment: &str) -> Result<link::Mention> {
|
pub async fn build_activity(c: &PlumeRocket, ment: &str) -> Result<link::Mention> {
|
||||||
let user = User::find_by_fqn(c, ment)?;
|
let user = User::find_by_fqn(c, ment).await?;
|
||||||
let mut mention = link::Mention::default();
|
let mut mention = link::Mention::default();
|
||||||
mention.link_props.set_href_string(user.ap_url)?;
|
mention.link_props.set_href_string(user.ap_url)?;
|
||||||
mention.link_props.set_name_string(format!("@{}", ment))?;
|
mention.link_props.set_name_string(format!("@{}", ment))?;
|
||||||
|
@ -20,20 +20,35 @@ mod module {
|
|||||||
pub flash_msg: Option<(String, String)>,
|
pub flash_msg: Option<(String, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for PlumeRocket {
|
impl<'a, 'r> FromRequest<'a, 'r> for PlumeRocket {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<PlumeRocket, ()> {
|
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
let conn = request.guard::<DbConn>()?;
|
let conn = DbConn::from_request(request).await.succeeded().unwrap();
|
||||||
let intl = request.guard::<rocket_i18n::I18n>()?;
|
let intl = rocket_i18n::I18n::from_request(request)
|
||||||
let user = request.guard::<users::User>().succeeded();
|
.await
|
||||||
let worker = request.guard::<'_, State<'_, Arc<ScheduledThreadPool>>>()?;
|
.succeeded()
|
||||||
let searcher = request.guard::<'_, State<'_, Arc<search::Searcher>>>()?;
|
.unwrap();
|
||||||
let flash_msg = request.guard::<FlashMessage<'_, '_>>().succeeded();
|
let user = users::User::from_request(request)
|
||||||
|
.await
|
||||||
|
.succeeded()
|
||||||
|
.unwrap();
|
||||||
|
let worker = request
|
||||||
|
.guard::<State<'_, Arc<ScheduledThreadPool>>>()
|
||||||
|
.await
|
||||||
|
.succeeded()
|
||||||
|
.unwrap();
|
||||||
|
let searcher = request
|
||||||
|
.guard::<State<'_, Arc<search::Searcher>>>()
|
||||||
|
.await
|
||||||
|
.succeeded()
|
||||||
|
.unwrap();
|
||||||
|
let flash_msg = request.guard::<FlashMessage<'_, '_>>().await.succeeded();
|
||||||
Outcome::Success(PlumeRocket {
|
Outcome::Success(PlumeRocket {
|
||||||
conn,
|
conn,
|
||||||
intl,
|
intl,
|
||||||
user,
|
user: Some(user),
|
||||||
flash_msg: flash_msg.map(|f| (f.name().into(), f.msg().into())),
|
flash_msg: flash_msg.map(|f| (f.name().into(), f.msg().into())),
|
||||||
worker: worker.clone(),
|
worker: worker.clone(),
|
||||||
searcher: searcher.clone(),
|
searcher: searcher.clone(),
|
||||||
@ -60,17 +75,18 @@ mod module {
|
|||||||
pub worker: Arc<ScheduledThreadPool>,
|
pub worker: Arc<ScheduledThreadPool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for PlumeRocket {
|
impl<'a, 'r> FromRequest<'a, 'r> for PlumeRocket {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<PlumeRocket, ()> {
|
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
let conn = request.guard::<DbConn>()?;
|
let conn = try_outcome!(DbConn::from_request(request).await);
|
||||||
let user = request.guard::<users::User>().succeeded();
|
let user = try_outcome!(users::User::from_request(request).await);
|
||||||
let worker = request.guard::<'_, State<'_, Arc<ScheduledThreadPool>>>()?;
|
let worker = try_outcome!(request.guard::<'_, State<'_, Arc<ScheduledThreadPool>>>());
|
||||||
let searcher = request.guard::<'_, State<'_, Arc<search::Searcher>>>()?;
|
let searcher = try_outcome!(request.guard::<'_, State<'_, Arc<search::Searcher>>>());
|
||||||
Outcome::Success(PlumeRocket {
|
Outcome::Success(PlumeRocket {
|
||||||
conn,
|
conn,
|
||||||
user,
|
user: Some(user),
|
||||||
worker: worker.clone(),
|
worker: worker.clone(),
|
||||||
searcher: searcher.clone(),
|
searcher: searcher.clone(),
|
||||||
})
|
})
|
||||||
|
@ -19,8 +19,8 @@ use plume_common::{
|
|||||||
},
|
},
|
||||||
utils::md_to_html,
|
utils::md_to_html,
|
||||||
};
|
};
|
||||||
use serde_json;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
pub type LicensedArticle = CustomObject<Licensed, Article>;
|
pub type LicensedArticle = CustomObject<Licensed, Article>;
|
||||||
|
|
||||||
@ -579,11 +579,11 @@ impl FromId<PlumeRocket> for Post {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let cover = article
|
let image = article.object_props.icon_object::<Image>().ok().unwrap();
|
||||||
.object_props
|
|
||||||
.icon_object::<Image>()
|
let mut r = Runtime::new().unwrap();
|
||||||
.ok()
|
let cover =
|
||||||
.and_then(|img| Media::from_activity(&c, &img).ok().map(|m| m.id));
|
Some(r.block_on(async { Media::from_activity(&c, &image).await.ok().unwrap().id }));
|
||||||
|
|
||||||
let title = article.object_props.name_string()?;
|
let title = article.object_props.name_string()?;
|
||||||
let post = Post::insert(
|
let post = Post::insert(
|
||||||
@ -699,17 +699,22 @@ impl FromId<PlumeRocket> for PostUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(c: &PlumeRocket, updated: LicensedArticle) -> Result<Self> {
|
fn from_activity(c: &PlumeRocket, updated: LicensedArticle) -> Result<Self> {
|
||||||
|
let image = updated
|
||||||
|
.object
|
||||||
|
.object_props
|
||||||
|
.icon_object::<Image>()
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
let mut r = Runtime::new().unwrap();
|
||||||
|
let cover =
|
||||||
|
Some(r.block_on(async { Media::from_activity(&c, &image).await.ok().unwrap().id }));
|
||||||
|
|
||||||
Ok(PostUpdate {
|
Ok(PostUpdate {
|
||||||
ap_url: updated.object.object_props.id_string()?,
|
ap_url: updated.object.object_props.id_string()?,
|
||||||
title: updated.object.object_props.name_string().ok(),
|
title: updated.object.object_props.name_string().ok(),
|
||||||
subtitle: updated.object.object_props.summary_string().ok(),
|
subtitle: updated.object.object_props.summary_string().ok(),
|
||||||
content: updated.object.object_props.content_string().ok(),
|
content: updated.object.object_props.content_string().ok(),
|
||||||
cover: updated
|
cover,
|
||||||
.object
|
|
||||||
.object_props
|
|
||||||
.icon_object::<Image>()
|
|
||||||
.ok()
|
|
||||||
.and_then(|img| Media::from_activity(&c, &img).ok().map(|m| m.id)),
|
|
||||||
source: updated
|
source: updated
|
||||||
.object
|
.object
|
||||||
.ap_object_props
|
.ap_object_props
|
||||||
|
@ -7,7 +7,9 @@ use crate::{
|
|||||||
users::User,
|
users::User,
|
||||||
PlumeRocket, Result,
|
PlumeRocket, Result,
|
||||||
};
|
};
|
||||||
|
use futures::stream::{self, StreamExt};
|
||||||
use plume_common::activity_pub::inbox::AsActor;
|
use plume_common::activity_pub::inbox::AsActor;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
use whatlang::{self, Lang};
|
use whatlang::{self, Lang};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@ -295,15 +297,33 @@ impl WithList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
List::Array(list) => match self {
|
List::Array(list) => match self {
|
||||||
WithList::Blog => Ok(list
|
WithList::Blog => {
|
||||||
.iter()
|
let mut rt = Runtime::new().unwrap();
|
||||||
.filter_map(|b| Blog::find_by_fqn(rocket, b).ok())
|
rt.block_on(async move {
|
||||||
.any(|b| b.id == post.blog_id)),
|
Ok(stream::iter(list)
|
||||||
|
.filter_map(|b| async move {
|
||||||
|
Some(Blog::find_by_fqn(rocket, b).await.ok().unwrap())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.any(|b| b.id == post.blog_id))
|
||||||
|
})
|
||||||
|
}
|
||||||
WithList::Author { boosts, likes } => match kind {
|
WithList::Author { boosts, likes } => match kind {
|
||||||
Kind::Original => Ok(list
|
Kind::Original => {
|
||||||
.iter()
|
let mut rt = Runtime::new().unwrap();
|
||||||
.filter_map(|a| User::find_by_fqn(rocket, a).ok())
|
rt.block_on(async move {
|
||||||
.any(|a| post.is_author(&rocket.conn, a.id).unwrap_or(false))),
|
Ok(stream::iter(list)
|
||||||
|
.filter_map(|a| async move {
|
||||||
|
Some(User::find_by_fqn(rocket, a).await.ok().unwrap())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.any(|a| post.is_author(&rocket.conn, a.id).unwrap_or(false)))
|
||||||
|
})
|
||||||
|
}
|
||||||
Kind::Reshare(u) => {
|
Kind::Reshare(u) => {
|
||||||
if *boosts {
|
if *boosts {
|
||||||
Ok(list.iter().any(|user| &u.fqn == user))
|
Ok(list.iter().any(|user| &u.fqn == user))
|
||||||
|
@ -11,7 +11,6 @@ use activitypub::{
|
|||||||
object::{Image, Tombstone},
|
object::{Image, Tombstone},
|
||||||
Activity, CustomObject, Endpoint,
|
Activity, CustomObject, Endpoint,
|
||||||
};
|
};
|
||||||
use bcrypt;
|
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
|
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
|
||||||
use openssl::{
|
use openssl::{
|
||||||
@ -37,7 +36,6 @@ use rocket::{
|
|||||||
outcome::IntoOutcome,
|
outcome::IntoOutcome,
|
||||||
request::{self, FromRequest, Request},
|
request::{self, FromRequest, Request},
|
||||||
};
|
};
|
||||||
use serde_json;
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp::PartialEq,
|
cmp::PartialEq,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
@ -191,7 +189,7 @@ impl User {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_fqn(c: &PlumeRocket, fqn: &str) -> Result<User> {
|
pub async fn find_by_fqn(c: &PlumeRocket, fqn: &str) -> Result<User> {
|
||||||
let from_db = users::table
|
let from_db = users::table
|
||||||
.filter(users::fqn.eq(fqn))
|
.filter(users::fqn.eq(fqn))
|
||||||
.first(&*c.conn)
|
.first(&*c.conn)
|
||||||
@ -199,12 +197,13 @@ impl User {
|
|||||||
if let Some(from_db) = from_db {
|
if let Some(from_db) = from_db {
|
||||||
Ok(from_db)
|
Ok(from_db)
|
||||||
} else {
|
} else {
|
||||||
User::fetch_from_webfinger(c, fqn)
|
User::fetch_from_webfinger(c, fqn).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_from_webfinger(c: &PlumeRocket, acct: &str) -> Result<User> {
|
async fn fetch_from_webfinger(c: &PlumeRocket, acct: &str) -> Result<User> {
|
||||||
let link = resolve(acct.to_owned(), true)?
|
let link = resolve(acct.to_owned(), true)
|
||||||
|
.await?
|
||||||
.links
|
.links
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||||
@ -212,8 +211,9 @@ impl User {
|
|||||||
User::from_id(c, link.href.as_ref()?, None).map_err(|(_, e)| e)
|
User::from_id(c, link.href.as_ref()?, None).map_err(|(_, e)| e)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_remote_interact_uri(acct: &str) -> Result<String> {
|
pub async fn fetch_remote_interact_uri(acct: &str) -> Result<String> {
|
||||||
resolve(acct.to_owned(), true)?
|
resolve(acct.to_owned(), true)
|
||||||
|
.await?
|
||||||
.links
|
.links
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|l| l.rel == "http://ostatus.org/schema/1.0/subscribe")
|
.find(|l| l.rel == "http://ostatus.org/schema/1.0/subscribe")
|
||||||
@ -221,9 +221,9 @@ impl User {
|
|||||||
.ok_or(Error::Webfinger)
|
.ok_or(Error::Webfinger)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(url: &str) -> Result<CustomPerson> {
|
async fn fetch(url: &str) -> Result<CustomPerson> {
|
||||||
let mut res = ClientBuilder::new()
|
let res = ClientBuilder::new()
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
.connect_timeout(std::time::Duration::from_secs(5))
|
||||||
.build()?
|
.build()?
|
||||||
.get(url)
|
.get(url)
|
||||||
.header(
|
.header(
|
||||||
@ -235,8 +235,9 @@ impl User {
|
|||||||
.join(", "),
|
.join(", "),
|
||||||
)?,
|
)?,
|
||||||
)
|
)
|
||||||
.send()?;
|
.send()
|
||||||
let text = &res.text()?;
|
.await?;
|
||||||
|
let text = &res.text().await?;
|
||||||
// without this workaround, publicKey is not correctly deserialized
|
// without this workaround, publicKey is not correctly deserialized
|
||||||
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
||||||
let mut json = serde_json::from_str::<CustomPerson>(text)?;
|
let mut json = serde_json::from_str::<CustomPerson>(text)?;
|
||||||
@ -244,48 +245,48 @@ impl User {
|
|||||||
Ok(json)
|
Ok(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_from_url(c: &PlumeRocket, url: &str) -> Result<User> {
|
pub async fn fetch_from_url(c: &PlumeRocket, url: &str) -> Result<User> {
|
||||||
User::fetch(url).and_then(|json| User::from_activity(c, json))
|
let json = User::fetch(url).await?;
|
||||||
|
User::from_activity(c, json)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refetch(&self, conn: &Connection) -> Result<()> {
|
pub async fn refetch(&self, conn: &Connection) -> Result<()> {
|
||||||
User::fetch(&self.ap_url.clone()).and_then(|json| {
|
let json = User::fetch(&self.ap_url.clone()).await?;
|
||||||
let avatar = Media::save_remote(
|
let avatar = Media::save_remote(
|
||||||
conn,
|
conn,
|
||||||
json.object
|
json.object
|
||||||
.object_props
|
.object_props
|
||||||
.icon_image()?
|
.icon_image()?
|
||||||
.object_props
|
.object_props
|
||||||
.url_string()?,
|
.url_string()?,
|
||||||
&self,
|
&self,
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
diesel::update(self)
|
diesel::update(self)
|
||||||
.set((
|
.set((
|
||||||
users::username.eq(json.object.ap_actor_props.preferred_username_string()?),
|
users::username.eq(json.object.ap_actor_props.preferred_username_string()?),
|
||||||
users::display_name.eq(json.object.object_props.name_string()?),
|
users::display_name.eq(json.object.object_props.name_string()?),
|
||||||
users::outbox_url.eq(json.object.ap_actor_props.outbox_string()?),
|
users::outbox_url.eq(json.object.ap_actor_props.outbox_string()?),
|
||||||
users::inbox_url.eq(json.object.ap_actor_props.inbox_string()?),
|
users::inbox_url.eq(json.object.ap_actor_props.inbox_string()?),
|
||||||
users::summary.eq(SafeString::new(
|
users::summary.eq(SafeString::new(
|
||||||
&json
|
&json
|
||||||
.object
|
.object
|
||||||
.object_props
|
.object_props
|
||||||
.summary_string()
|
.summary_string()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
)),
|
)),
|
||||||
users::followers_endpoint.eq(json.object.ap_actor_props.followers_string()?),
|
users::followers_endpoint.eq(json.object.ap_actor_props.followers_string()?),
|
||||||
users::avatar_id.eq(avatar.map(|a| a.id)),
|
users::avatar_id.eq(avatar.map(|a| a.id)),
|
||||||
users::last_fetched_date.eq(Utc::now().naive_utc()),
|
users::last_fetched_date.eq(Utc::now().naive_utc()),
|
||||||
users::public_key.eq(json
|
users::public_key.eq(json
|
||||||
.custom_props
|
.custom_props
|
||||||
.public_key_publickey()?
|
.public_key_publickey()?
|
||||||
.public_key_pem_string()?),
|
.public_key_pem_string()?),
|
||||||
))
|
))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash_pass(pass: &str) -> Result<String> {
|
pub fn hash_pass(pass: &str) -> Result<String> {
|
||||||
@ -356,9 +357,10 @@ impl User {
|
|||||||
.set_part_of_link(Id::new(&self.outbox_url))?;
|
.set_part_of_link(Id::new(&self.outbox_url))?;
|
||||||
Ok(ActivityStream::new(coll))
|
Ok(ActivityStream::new(coll))
|
||||||
}
|
}
|
||||||
fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
|
|
||||||
let mut res = ClientBuilder::new()
|
async fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
let res = ClientBuilder::new()
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(5))
|
||||||
.build()?
|
.build()?
|
||||||
.get(url)
|
.get(url)
|
||||||
.header(
|
.header(
|
||||||
@ -370,8 +372,9 @@ impl User {
|
|||||||
.join(", "),
|
.join(", "),
|
||||||
)?,
|
)?,
|
||||||
)
|
)
|
||||||
.send()?;
|
.send()
|
||||||
let text = &res.text()?;
|
.await?;
|
||||||
|
let text = &res.text().await?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
let items = json["items"]
|
let items = json["items"]
|
||||||
.as_array()
|
.as_array()
|
||||||
@ -386,9 +389,9 @@ impl User {
|
|||||||
};
|
};
|
||||||
Ok((items, next))
|
Ok((items, next))
|
||||||
}
|
}
|
||||||
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
|
pub async fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
|
||||||
let mut res = ClientBuilder::new()
|
let res = ClientBuilder::new()
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
.connect_timeout(std::time::Duration::from_secs(5))
|
||||||
.build()?
|
.build()?
|
||||||
.get(&self.outbox_url[..])
|
.get(&self.outbox_url[..])
|
||||||
.header(
|
.header(
|
||||||
@ -400,13 +403,14 @@ impl User {
|
|||||||
.join(", "),
|
.join(", "),
|
||||||
)?,
|
)?,
|
||||||
)
|
)
|
||||||
.send()?;
|
.send()
|
||||||
let text = &res.text()?;
|
.await?;
|
||||||
|
let text = &res.text().await?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
if let Some(first) = json.get("first") {
|
if let Some(first) = json.get("first") {
|
||||||
let mut items: Vec<T> = Vec::new();
|
let mut items: Vec<T> = Vec::new();
|
||||||
let mut next = first.as_str().unwrap().to_owned();
|
let mut next = first.as_str().unwrap().to_owned();
|
||||||
while let Ok((mut page, nxt)) = self.fetch_outbox_page(&next) {
|
while let Ok((mut page, nxt)) = self.fetch_outbox_page(&next).await {
|
||||||
if page.is_empty() {
|
if page.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -431,9 +435,9 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_followers_ids(&self) -> Result<Vec<String>> {
|
pub async fn fetch_followers_ids(&self) -> Result<Vec<String>> {
|
||||||
let mut res = ClientBuilder::new()
|
let res = ClientBuilder::new()
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
.connect_timeout(std::time::Duration::from_secs(5))
|
||||||
.build()?
|
.build()?
|
||||||
.get(&self.followers_endpoint[..])
|
.get(&self.followers_endpoint[..])
|
||||||
.header(
|
.header(
|
||||||
@ -445,8 +449,9 @@ impl User {
|
|||||||
.join(", "),
|
.join(", "),
|
||||||
)?,
|
)?,
|
||||||
)
|
)
|
||||||
.send()?;
|
.send()
|
||||||
let text = &res.text()?;
|
.await?;
|
||||||
|
let text = &res.text().await?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
Ok(json["items"]
|
Ok(json["items"]
|
||||||
.as_array()
|
.as_array()
|
||||||
@ -789,11 +794,12 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for User {
|
impl<'a, 'r> FromRequest<'a, 'r> for User {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<User, ()> {
|
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
let conn = request.guard::<DbConn>()?;
|
let conn = try_outcome!(DbConn::from_request(request).await);
|
||||||
request
|
request
|
||||||
.cookies()
|
.cookies()
|
||||||
.get_private(AUTH_COOKIE)
|
.get_private(AUTH_COOKIE)
|
||||||
|
@ -10,7 +10,8 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
|
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
|
||||||
|
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
|
||||||
"X-Crowdin-Project: plume\n"
|
"X-Crowdin-Project: plume\n"
|
||||||
"X-Crowdin-Language: ar\n"
|
"X-Crowdin-Language: ar\n"
|
||||||
"X-Crowdin-File: /master/po/plume/plume.pot\n"
|
"X-Crowdin-File: /master/po/plume/plume.pot\n"
|
||||||
|
@ -1 +1 @@
|
|||||||
nightly-2020-01-15
|
nightly-2020-05-05
|
||||||
|
@ -35,6 +35,7 @@ impl Scope for plume_models::posts::Post {
|
|||||||
|
|
||||||
pub struct Authorization<A, S>(pub ApiToken, PhantomData<(A, S)>);
|
pub struct Authorization<A, S>(pub ApiToken, PhantomData<(A, S)>);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r, A, S> FromRequest<'a, 'r> for Authorization<A, S>
|
impl<'a, 'r, A, S> FromRequest<'a, 'r> for Authorization<A, S>
|
||||||
where
|
where
|
||||||
A: Action,
|
A: Action,
|
||||||
@ -42,9 +43,10 @@ where
|
|||||||
{
|
{
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Authorization<A, S>, ()> {
|
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Authorization<A, S>, ()> {
|
||||||
request
|
request
|
||||||
.guard::<ApiToken>()
|
.guard::<ApiToken>()
|
||||||
|
.await
|
||||||
.map_failure(|_| (Status::Unauthorized, ()))
|
.map_failure(|_| (Status::Unauthorized, ()))
|
||||||
.and_then(|token| {
|
.and_then(|token| {
|
||||||
if token.can(A::to_str(), S::to_str()) {
|
if token.can(A::to_str(), S::to_str()) {
|
||||||
|
@ -4,7 +4,6 @@ use rocket::{
|
|||||||
response::{self, Responder},
|
response::{self, Responder},
|
||||||
};
|
};
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
use plume_common::utils::random_hex;
|
use plume_common::utils::random_hex;
|
||||||
use plume_models::{api_tokens::*, apps::App, users::User, Error, PlumeRocket};
|
use plume_models::{api_tokens::*, apps::App, users::User, Error, PlumeRocket};
|
||||||
@ -26,21 +25,31 @@ impl From<std::option::NoneError> for ApiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'r> Responder<'r> for ApiError {
|
impl<'r> Responder<'r> for ApiError {
|
||||||
fn respond_to(self, req: &Request<'_>) -> response::Result<'r> {
|
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Error::NotFound => Json(json!({
|
Error::NotFound => {
|
||||||
"error": "Not found"
|
Json(json!({
|
||||||
}))
|
"error": "Not found"
|
||||||
.respond_to(req),
|
}))
|
||||||
Error::Unauthorized => Json(json!({
|
.respond_to(req)
|
||||||
"error": "You are not authorized to access this resource"
|
.await
|
||||||
}))
|
}
|
||||||
.respond_to(req),
|
Error::Unauthorized => {
|
||||||
_ => Json(json!({
|
Json(json!({
|
||||||
"error": "Server error"
|
"error": "You are not authorized to access this resource"
|
||||||
}))
|
}))
|
||||||
.respond_to(req),
|
.respond_to(req)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
Json(json!({
|
||||||
|
"error": "Server error"
|
||||||
|
}))
|
||||||
|
.respond_to(req)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,14 +64,14 @@ pub struct OAuthRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/oauth2?<query..>")]
|
#[get("/oauth2?<query..>")]
|
||||||
pub fn oauth(
|
pub async fn oauth(
|
||||||
query: Form<OAuthRequest>,
|
query: Form<OAuthRequest>,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let app = App::find_by_client_id(conn, &query.client_id)?;
|
let app = App::find_by_client_id(conn, &query.client_id)?;
|
||||||
if app.client_secret == query.client_secret {
|
if app.client_secret == query.client_secret {
|
||||||
if let Ok(user) = User::find_by_fqn(&rockets, &query.username) {
|
if let Ok(user) = User::find_by_fqn(&rockets, &query.username).await {
|
||||||
if user.auth(&query.password) {
|
if user.auth(&query.password) {
|
||||||
let token = ApiToken::insert(
|
let token = ApiToken::insert(
|
||||||
conn,
|
conn,
|
||||||
|
@ -98,7 +98,7 @@ pub fn list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/posts", data = "<payload>")]
|
#[post("/posts", data = "<payload>")]
|
||||||
pub fn create(
|
pub async fn create(
|
||||||
auth: Authorization<Write, Post>,
|
auth: Authorization<Write, Post>,
|
||||||
payload: Json<NewPostData>,
|
payload: Json<NewPostData>,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
@ -192,7 +192,7 @@ pub fn create(
|
|||||||
for m in mentions.into_iter() {
|
for m in mentions.into_iter() {
|
||||||
Mention::from_activity(
|
Mention::from_activity(
|
||||||
&*conn,
|
&*conn,
|
||||||
&Mention::build_activity(&rockets, &m)?,
|
&Mention::build_activity(&rockets, &m).await?,
|
||||||
post.id,
|
post.id,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
44
src/inbox.rs
44
src/inbox.rs
@ -9,9 +9,9 @@ use plume_models::{
|
|||||||
use rocket::{data::*, http::Status, response::status, Outcome::*, Request};
|
use rocket::{data::*, http::Status, response::status, Outcome::*, Request};
|
||||||
use rocket_contrib::json::*;
|
use rocket_contrib::json::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::io::Read;
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
pub fn handle_incoming(
|
pub async fn handle_incoming(
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
data: SignedJson<serde_json::Value>,
|
data: SignedJson<serde_json::Value>,
|
||||||
headers: Headers<'_>,
|
headers: Headers<'_>,
|
||||||
@ -32,6 +32,7 @@ pub fn handle_incoming(
|
|||||||
// maybe we just know an old key?
|
// maybe we just know an old key?
|
||||||
actor
|
actor
|
||||||
.refetch(conn)
|
.refetch(conn)
|
||||||
|
.await
|
||||||
.and_then(|_| User::get(conn, actor.id))
|
.and_then(|_| User::get(conn, actor.id))
|
||||||
.and_then(|u| {
|
.and_then(|u| {
|
||||||
if verify_http_headers(&u, &headers.0, &sig).is_secure() || act.clone().verify(&u) {
|
if verify_http_headers(&u, &headers.0, &sig).is_secure() || act.clone().verify(&u) {
|
||||||
@ -73,32 +74,31 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for SignedJson<T> {
|
|||||||
type Owned = String;
|
type Owned = String;
|
||||||
type Borrowed = str;
|
type Borrowed = str;
|
||||||
|
|
||||||
fn transform(
|
fn transform<'r>(r: &'r Request, d: Data) -> TransformFuture<'r, Self::Owned, Self::Error> {
|
||||||
r: &Request<'_>,
|
Box::pin(async move {
|
||||||
d: Data,
|
let size_limit = r.limits().get("json").unwrap_or(JSON_LIMIT);
|
||||||
) -> Transform<rocket::data::Outcome<Self::Owned, Self::Error>> {
|
let mut s = String::with_capacity(512);
|
||||||
let size_limit = r.limits().get("json").unwrap_or(JSON_LIMIT);
|
let outcome = match d.open().take(size_limit).read_to_string(&mut s).await {
|
||||||
let mut s = String::with_capacity(512);
|
Ok(_) => Success(s),
|
||||||
match d.open().take(size_limit).read_to_string(&mut s) {
|
Err(e) => Failure((Status::BadRequest, JsonError::Io(e))),
|
||||||
Ok(_) => Transform::Borrowed(Success(s)),
|
};
|
||||||
Err(e) => Transform::Borrowed(Failure((Status::BadRequest, JsonError::Io(e)))),
|
Transform::Borrowed(outcome)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_data(
|
fn from_data(
|
||||||
_: &Request<'_>,
|
_: &Request<'_>,
|
||||||
o: Transformed<'a, Self>,
|
o: Transformed<'a, Self>,
|
||||||
) -> rocket::data::Outcome<Self, Self::Error> {
|
) -> FromDataFuture<'a, Self, Self::Error> {
|
||||||
let string = o.borrowed()?;
|
Box::pin(async move {
|
||||||
match serde_json::from_str(&string) {
|
let string = try_outcome!(o.borrowed());
|
||||||
Ok(v) => Success(SignedJson(Digest::from_body(&string), Json(v))),
|
match serde_json::from_str(&string) {
|
||||||
Err(e) => {
|
Ok(v) => Success(SignedJson(Digest::from_body(&string), Json(v))),
|
||||||
if e.is_data() {
|
Err(e) if e.is_data() => {
|
||||||
Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
|
return Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
|
||||||
} else {
|
|
||||||
Failure((Status::BadRequest, JsonError::Parse(string, e)))
|
|
||||||
}
|
}
|
||||||
|
Err(e) => Failure((Status::BadRequest, JsonError::Parse(string, e))),
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
src/main.rs
26
src/main.rs
@ -1,16 +1,15 @@
|
|||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
#![feature(decl_macro, proc_macro_hygiene, try_trait)]
|
#![feature(proc_macro_hygiene, try_trait)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate gettext_macros;
|
extern crate gettext_macros;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate runtime_fmt;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate validator_derive;
|
extern crate validator_derive;
|
||||||
|
extern crate validator;
|
||||||
|
|
||||||
use clap::App;
|
use clap::App;
|
||||||
use diesel::r2d2::ConnectionManager;
|
use diesel::r2d2::ConnectionManager;
|
||||||
@ -21,7 +20,6 @@ use plume_models::{
|
|||||||
search::{Searcher as UnmanagedSearcher, SearcherError},
|
search::{Searcher as UnmanagedSearcher, SearcherError},
|
||||||
Connection, Error, CONFIG,
|
Connection, Error, CONFIG,
|
||||||
};
|
};
|
||||||
use rocket_csrf::CsrfFairingBuilder;
|
|
||||||
use scheduled_thread_pool::ScheduledThreadPool;
|
use scheduled_thread_pool::ScheduledThreadPool;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@ -275,25 +273,7 @@ Then try to restart Plume
|
|||||||
.manage(dbpool)
|
.manage(dbpool)
|
||||||
.manage(Arc::new(workpool))
|
.manage(Arc::new(workpool))
|
||||||
.manage(searcher)
|
.manage(searcher)
|
||||||
.manage(include_i18n!())
|
.manage(include_i18n!());
|
||||||
.attach(
|
|
||||||
CsrfFairingBuilder::new()
|
|
||||||
.set_default_target(
|
|
||||||
"/csrf-violation?target=<uri>".to_owned(),
|
|
||||||
rocket::http::Method::Post,
|
|
||||||
)
|
|
||||||
.add_exceptions(vec![
|
|
||||||
("/inbox".to_owned(), "/inbox".to_owned(), None),
|
|
||||||
(
|
|
||||||
"/@/<name>/inbox".to_owned(),
|
|
||||||
"/@/<name>/inbox".to_owned(),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
("/api/<path..>".to_owned(), "/api/<path..>".to_owned(), None),
|
|
||||||
])
|
|
||||||
.finalize()
|
|
||||||
.expect("main: csrf fairing creation error"),
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(feature = "test")]
|
#[cfg(feature = "test")]
|
||||||
let rocket = rocket.mount("/test", routes![test_routes::health,]);
|
let rocket = rocket.mount("/test", routes![test_routes::health,]);
|
||||||
|
@ -19,10 +19,14 @@ use plume_models::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[get("/~/<name>?<page>", rank = 2)]
|
#[get("/~/<name>?<page>", rank = 2)]
|
||||||
pub fn details(name: String, page: Option<Page>, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
pub async fn details(
|
||||||
|
name: String,
|
||||||
|
page: Option<Page>,
|
||||||
|
rockets: PlumeRocket,
|
||||||
|
) -> Result<Ructe, ErrorPage> {
|
||||||
let page = page.unwrap_or_default();
|
let page = page.unwrap_or_default();
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let blog = Blog::find_by_fqn(&rockets, &name)?;
|
let blog = Blog::find_by_fqn(&rockets, &name).await?;
|
||||||
let posts = Post::blog_page(conn, &blog, page.limits())?;
|
let posts = Post::blog_page(conn, &blog, page.limits())?;
|
||||||
let articles_count = Post::count_for_blog(conn, &blog)?;
|
let articles_count = Post::count_for_blog(conn, &blog)?;
|
||||||
let authors = &blog.list_authors(conn)?;
|
let authors = &blog.list_authors(conn)?;
|
||||||
@ -38,12 +42,12 @@ pub fn details(name: String, page: Option<Page>, rockets: PlumeRocket) -> Result
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/~/<name>", rank = 1)]
|
#[get("/~/<name>", rank = 1)]
|
||||||
pub fn activity_details(
|
pub async fn activity_details(
|
||||||
name: String,
|
name: String,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
_ap: ApRequest,
|
_ap: ApRequest,
|
||||||
) -> Option<ActivityStream<CustomGroup>> {
|
) -> Option<ActivityStream<CustomGroup>> {
|
||||||
let blog = Blog::find_by_fqn(&rockets, &name).ok()?;
|
let blog = Blog::find_by_fqn(&rockets, &name).await?;
|
||||||
Some(ActivityStream::new(blog.to_activity(&*rockets.conn).ok()?))
|
Some(ActivityStream::new(blog.to_activity(&*rockets.conn).ok()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +87,7 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/blogs/new", data = "<form>")]
|
#[post("/blogs/new", data = "<form>")]
|
||||||
pub fn create(form: LenientForm<NewBlogForm>, rockets: PlumeRocket) -> RespondOrRedirect {
|
pub async fn create(form: LenientForm<NewBlogForm>, rockets: PlumeRocket) -> RespondOrRedirect {
|
||||||
let slug = utils::make_actor_id(&form.title);
|
let slug = utils::make_actor_id(&form.title);
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let intl = &rockets.intl.catalog;
|
let intl = &rockets.intl.catalog;
|
||||||
@ -93,7 +97,7 @@ pub fn create(form: LenientForm<NewBlogForm>, rockets: PlumeRocket) -> RespondOr
|
|||||||
Ok(_) => ValidationErrors::new(),
|
Ok(_) => ValidationErrors::new(),
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
if Blog::find_by_fqn(&rockets, &slug).is_ok() {
|
if Blog::find_by_fqn(&rockets, &slug).await.is_ok() {
|
||||||
errors.add(
|
errors.add(
|
||||||
"title",
|
"title",
|
||||||
ValidationError {
|
ValidationError {
|
||||||
@ -143,9 +147,11 @@ pub fn create(form: LenientForm<NewBlogForm>, rockets: PlumeRocket) -> RespondOr
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/~/<name>/delete")]
|
#[post("/~/<name>/delete")]
|
||||||
pub fn delete(name: String, rockets: PlumeRocket) -> RespondOrRedirect {
|
pub async fn delete(name: String, rockets: PlumeRocket) -> RespondOrRedirect {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let blog = Blog::find_by_fqn(&rockets, &name).expect("blog::delete: blog not found");
|
let blog = Blog::find_by_fqn(&rockets, &name)
|
||||||
|
.await
|
||||||
|
.expect("blog::delete: blog not found");
|
||||||
|
|
||||||
if rockets
|
if rockets
|
||||||
.user
|
.user
|
||||||
@ -184,9 +190,9 @@ pub struct EditForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/~/<name>/edit")]
|
#[get("/~/<name>/edit")]
|
||||||
pub fn edit(name: String, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
pub async fn edit(name: String, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let blog = Blog::find_by_fqn(&rockets, &name)?;
|
let blog = Blog::find_by_fqn(&rockets, &name).await?;
|
||||||
if rockets
|
if rockets
|
||||||
.user
|
.user
|
||||||
.clone()
|
.clone()
|
||||||
@ -233,14 +239,16 @@ fn check_media(conn: &Connection, id: i32, user: &User) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[put("/~/<name>/edit", data = "<form>")]
|
#[put("/~/<name>/edit", data = "<form>")]
|
||||||
pub fn update(
|
pub async fn update(
|
||||||
name: String,
|
name: String,
|
||||||
form: LenientForm<EditForm>,
|
form: LenientForm<EditForm>,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> RespondOrRedirect {
|
) -> RespondOrRedirect {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let intl = &rockets.intl.catalog;
|
let intl = &rockets.intl.catalog;
|
||||||
let mut blog = Blog::find_by_fqn(&rockets, &name).expect("blog::update: blog not found");
|
let mut blog = Blog::find_by_fqn(&rockets, &name)
|
||||||
|
.await
|
||||||
|
.expect("blog::update: blog not found");
|
||||||
if !rockets
|
if !rockets
|
||||||
.user
|
.user
|
||||||
.clone()
|
.clone()
|
||||||
@ -342,23 +350,28 @@ pub fn update(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/~/<name>/outbox")]
|
#[get("/~/<name>/outbox")]
|
||||||
pub fn outbox(name: String, rockets: PlumeRocket) -> Option<ActivityStream<OrderedCollection>> {
|
pub async fn outbox(
|
||||||
let blog = Blog::find_by_fqn(&rockets, &name).ok()?;
|
name: String,
|
||||||
|
rockets: PlumeRocket,
|
||||||
|
) -> Option<ActivityStream<OrderedCollection>> {
|
||||||
|
let blog = Blog::find_by_fqn(&rockets, &name).await?;
|
||||||
Some(blog.outbox(&*rockets.conn).ok()?)
|
Some(blog.outbox(&*rockets.conn).ok()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[get("/~/<name>/outbox?<page>")]
|
#[get("/~/<name>/outbox?<page>")]
|
||||||
pub fn outbox_page(
|
pub async fn outbox_page(
|
||||||
name: String,
|
name: String,
|
||||||
page: Page,
|
page: Page,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Option<ActivityStream<OrderedCollectionPage>> {
|
) -> Option<ActivityStream<OrderedCollectionPage>> {
|
||||||
let blog = Blog::find_by_fqn(&rockets, &name).ok()?;
|
let blog = Blog::find_by_fqn(&rockets, &name).await?;
|
||||||
Some(blog.outbox_page(&*rockets.conn, page.limits()).ok()?)
|
Some(blog.outbox_page(&*rockets.conn, page.limits()).ok()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/~/<name>/atom.xml")]
|
#[get("/~/<name>/atom.xml")]
|
||||||
pub fn atom_feed(name: String, rockets: PlumeRocket) -> Option<Content<String>> {
|
pub async fn atom_feed(name: String, rockets: PlumeRocket) -> Option<Content<String>> {
|
||||||
let blog = Blog::find_by_fqn(&rockets, &name).ok()?;
|
let blog = Blog::find_by_fqn(&rockets, &name).await?;
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let entries = Post::get_recents_for_blog(&*conn, &blog, 15).ok()?;
|
let entries = Post::get_recents_for_blog(&*conn, &blog, 15).ok()?;
|
||||||
let uri = Instance::get_local()
|
let uri = Instance::get_local()
|
||||||
|
@ -22,13 +22,13 @@ use plume_models::{
|
|||||||
#[derive(Default, FromForm, Debug, Validate)]
|
#[derive(Default, FromForm, Debug, Validate)]
|
||||||
pub struct NewCommentForm {
|
pub struct NewCommentForm {
|
||||||
pub responding_to: Option<i32>,
|
pub responding_to: Option<i32>,
|
||||||
#[validate(length(min = "1", message = "Your comment can't be empty"))]
|
#[validate(length(min = 1, message = "Your comment can't be empty"))]
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub warning: String,
|
pub warning: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/~/<blog_name>/<slug>/comment", data = "<form>")]
|
#[post("/~/<blog_name>/<slug>/comment", data = "<form>")]
|
||||||
pub fn create(
|
pub async fn create(
|
||||||
blog_name: String,
|
blog_name: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
form: LenientForm<NewCommentForm>,
|
form: LenientForm<NewCommentForm>,
|
||||||
@ -36,10 +36,12 @@ pub fn create(
|
|||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<Flash<Redirect>, Ructe> {
|
) -> Result<Flash<Redirect>, Ructe> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let blog = Blog::find_by_fqn(&rockets, &blog_name).expect("comments::create: blog error");
|
let blog = Blog::find_by_fqn(&rockets, &blog_name)
|
||||||
|
.await
|
||||||
|
.expect("comments::create: blog error");
|
||||||
let post = Post::find_by_slug(&*conn, &slug, blog.id).expect("comments::create: post error");
|
let post = Post::find_by_slug(&*conn, &slug, blog.id).expect("comments::create: post error");
|
||||||
form.validate()
|
match form.validate() {
|
||||||
.map(|_| {
|
Ok(_ok) => {
|
||||||
let (html, mentions, _hashtags) = utils::md_to_html(
|
let (html, mentions, _hashtags) = utils::md_to_html(
|
||||||
form.content.as_ref(),
|
form.content.as_ref(),
|
||||||
Some(
|
Some(
|
||||||
@ -66,6 +68,7 @@ pub fn create(
|
|||||||
.expect("comments::create: insert error");
|
.expect("comments::create: insert error");
|
||||||
let new_comment = comm
|
let new_comment = comm
|
||||||
.create_activity(&rockets)
|
.create_activity(&rockets)
|
||||||
|
.await
|
||||||
.expect("comments::create: activity error");
|
.expect("comments::create: activity error");
|
||||||
|
|
||||||
// save mentions
|
// save mentions
|
||||||
@ -73,6 +76,7 @@ pub fn create(
|
|||||||
Mention::from_activity(
|
Mention::from_activity(
|
||||||
&*conn,
|
&*conn,
|
||||||
&Mention::build_activity(&rockets, &ment)
|
&Mention::build_activity(&rockets, &ment)
|
||||||
|
.await
|
||||||
.expect("comments::create: build mention error"),
|
.expect("comments::create: build mention error"),
|
||||||
comm.id,
|
comm.id,
|
||||||
false,
|
false,
|
||||||
@ -90,14 +94,14 @@ pub fn create(
|
|||||||
.worker
|
.worker
|
||||||
.execute(move || broadcast(&user_clone, new_comment, dest));
|
.execute(move || broadcast(&user_clone, new_comment, dest));
|
||||||
|
|
||||||
Flash::success(
|
Ok(Flash::success(
|
||||||
Redirect::to(
|
Redirect::to(
|
||||||
uri!(super::posts::details: blog = blog_name, slug = slug, responding_to = _),
|
uri!(super::posts::details: blog = blog_name, slug = slug, responding_to = _),
|
||||||
),
|
),
|
||||||
i18n!(&rockets.intl.catalog, "Your comment has been posted."),
|
i18n!(&rockets.intl.catalog, "Your comment has been posted."),
|
||||||
)
|
))
|
||||||
})
|
}
|
||||||
.map_err(|errors| {
|
Err(errors) => {
|
||||||
// TODO: de-duplicate this code
|
// TODO: de-duplicate this code
|
||||||
let comments = CommentTree::from_post(&*conn, &post, Some(&user))
|
let comments = CommentTree::from_post(&*conn, &post, Some(&user))
|
||||||
.expect("comments::create: comments error");
|
.expect("comments::create: comments error");
|
||||||
@ -106,7 +110,7 @@ pub fn create(
|
|||||||
.responding_to
|
.responding_to
|
||||||
.and_then(|r| Comment::get(&*conn, r).ok());
|
.and_then(|r| Comment::get(&*conn, r).ok());
|
||||||
|
|
||||||
render!(posts::details(
|
Err(render!(posts::details(
|
||||||
&rockets.to_context(),
|
&rockets.to_context(),
|
||||||
post.clone(),
|
post.clone(),
|
||||||
blog,
|
blog,
|
||||||
@ -133,8 +137,9 @@ pub fn create(
|
|||||||
post.get_authors(&*conn)
|
post.get_authors(&*conn)
|
||||||
.expect("comments::create: authors error")[0]
|
.expect("comments::create: authors error")[0]
|
||||||
.clone()
|
.clone()
|
||||||
))
|
)))
|
||||||
})
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/~/<blog>/<slug>/comment/<id>/delete")]
|
#[post("/~/<blog>/<slug>/comment/<id>/delete")]
|
||||||
@ -174,15 +179,16 @@ pub fn delete(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/~/<_blog>/<_slug>/comment/<id>")]
|
#[get("/~/<_blog>/<_slug>/comment/<id>")]
|
||||||
pub fn activity_pub(
|
pub async fn activity_pub(
|
||||||
_blog: String,
|
_blog: String,
|
||||||
_slug: String,
|
_slug: String,
|
||||||
id: i32,
|
id: i32,
|
||||||
_ap: ApRequest,
|
_ap: ApRequest,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Option<ActivityStream<Note>> {
|
) -> Option<ActivityStream<Note>> {
|
||||||
Comment::get(&*rockets.conn, id)
|
let c = match Comment::get(&*rockets.conn, id) {
|
||||||
.and_then(|c| c.to_activity(&rockets))
|
Ok(c) => c.to_activity(&rockets).await.ok(),
|
||||||
.ok()
|
Err(_) => None,
|
||||||
.map(ActivityStream::new)
|
};
|
||||||
|
c.map(ActivityStream::new)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::template_utils::{IntoContext, Ructe};
|
use crate::template_utils::{IntoContext, Ructe};
|
||||||
use plume_models::{Error, PlumeRocket};
|
use plume_models::{Error, PlumeRocket};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
|
request::FromRequest,
|
||||||
response::{self, Responder},
|
response::{self, Responder},
|
||||||
Request,
|
Request,
|
||||||
};
|
};
|
||||||
@ -14,35 +15,46 @@ impl From<Error> for ErrorPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'r> Responder<'r> for ErrorPage {
|
impl<'r> Responder<'r> for ErrorPage {
|
||||||
fn respond_to(self, req: &Request<'_>) -> response::Result<'r> {
|
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
|
||||||
let rockets = req.guard::<PlumeRocket>().unwrap();
|
let rockets = PlumeRocket::from_request(req).await.unwrap();
|
||||||
|
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Error::NotFound => render!(errors::not_found(&rockets.to_context())).respond_to(req),
|
Error::NotFound => {
|
||||||
Error::Unauthorized => {
|
render!(errors::not_found(&rockets.to_context()))
|
||||||
render!(errors::not_found(&rockets.to_context())).respond_to(req)
|
.respond_to(req)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Error::Unauthorized => {
|
||||||
|
render!(errors::not_found(&rockets.to_context()))
|
||||||
|
.respond_to(req)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
render!(errors::not_found(&rockets.to_context()))
|
||||||
|
.respond_to(req)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
_ => render!(errors::not_found(&rockets.to_context())).respond_to(req),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[catch(404)]
|
#[catch(404)]
|
||||||
pub fn not_found(req: &Request<'_>) -> Ructe {
|
pub async fn not_found(req: &Request<'_>) -> Ructe {
|
||||||
let rockets = req.guard::<PlumeRocket>().unwrap();
|
let rockets = req.guard::<PlumeRocket>().await.unwrap();
|
||||||
render!(errors::not_found(&rockets.to_context()))
|
render!(errors::not_found(&rockets.to_context()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[catch(422)]
|
#[catch(422)]
|
||||||
pub fn unprocessable_entity(req: &Request<'_>) -> Ructe {
|
pub async fn unprocessable_entity(req: &Request<'_>) -> Ructe {
|
||||||
let rockets = req.guard::<PlumeRocket>().unwrap();
|
let rockets = req.guard::<PlumeRocket>().await.unwrap();
|
||||||
render!(errors::unprocessable_entity(&rockets.to_context()))
|
render!(errors::unprocessable_entity(&rockets.to_context()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[catch(500)]
|
#[catch(500)]
|
||||||
pub fn server_error(req: &Request<'_>) -> Ructe {
|
pub async fn server_error(req: &Request<'_>) -> Ructe {
|
||||||
let rockets = req.guard::<PlumeRocket>().unwrap();
|
let rockets = req.guard::<PlumeRocket>().await.unwrap();
|
||||||
render!(errors::server_error(&rockets.to_context()))
|
render!(errors::server_error(&rockets.to_context()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use rocket::{
|
|||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
use rocket_i18n::I18n;
|
use rocket_i18n::I18n;
|
||||||
use scheduled_thread_pool::ScheduledThreadPool;
|
use scheduled_thread_pool::ScheduledThreadPool;
|
||||||
use serde_json;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use validator::{Validate, ValidationErrors};
|
use validator::{Validate, ValidationErrors};
|
||||||
|
|
||||||
@ -76,12 +76,12 @@ pub fn admin_mod(_mod: Moderator, rockets: PlumeRocket) -> Ructe {
|
|||||||
|
|
||||||
#[derive(Clone, FromForm, Validate)]
|
#[derive(Clone, FromForm, Validate)]
|
||||||
pub struct InstanceSettingsForm {
|
pub struct InstanceSettingsForm {
|
||||||
#[validate(length(min = "1"))]
|
#[validate(length(min = 1))]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub open_registrations: bool,
|
pub open_registrations: bool,
|
||||||
pub short_description: SafeString,
|
pub short_description: SafeString,
|
||||||
pub long_description: SafeString,
|
pub long_description: SafeString,
|
||||||
#[validate(length(min = "1"))]
|
#[validate(length(min = 1))]
|
||||||
pub default_license: String,
|
pub default_license: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,17 +386,21 @@ fn ban(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/inbox", data = "<data>")]
|
#[post("/inbox", data = "<data>")]
|
||||||
pub fn shared_inbox(
|
pub async fn shared_inbox(
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
data: inbox::SignedJson<serde_json::Value>,
|
data: inbox::SignedJson<serde_json::Value>,
|
||||||
headers: Headers<'_>,
|
headers: Headers<'_>,
|
||||||
) -> Result<String, status::BadRequest<&'static str>> {
|
) -> Result<String, status::BadRequest<&'static str>> {
|
||||||
inbox::handle_incoming(rockets, data, headers)
|
inbox::handle_incoming(rockets, data, headers).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/remote_interact?<target>")]
|
#[get("/remote_interact?<target>")]
|
||||||
pub fn interact(rockets: PlumeRocket, user: Option<User>, target: String) -> Option<Redirect> {
|
pub async fn interact(
|
||||||
if User::find_by_fqn(&rockets, &target).is_ok() {
|
rockets: PlumeRocket,
|
||||||
|
user: Option<User>,
|
||||||
|
target: String,
|
||||||
|
) -> Option<Redirect> {
|
||||||
|
if User::find_by_fqn(&rockets, &target).await.is_ok() {
|
||||||
return Some(Redirect::to(uri!(super::user::details: name = target)));
|
return Some(Redirect::to(uri!(super::user::details: name = target)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,9 +7,8 @@ use chrono::naive::NaiveDateTime;
|
|||||||
use plume_models::{posts::Post, Connection, CONFIG, ITEMS_PER_PAGE};
|
use plume_models::{posts::Post, Connection, CONFIG, ITEMS_PER_PAGE};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
http::{
|
http::{
|
||||||
hyper::header::{CacheControl, CacheDirective, ETag, EntityTag},
|
|
||||||
uri::{FromUriParam, Query},
|
uri::{FromUriParam, Query},
|
||||||
RawStr, Status,
|
Header, RawStr, Status,
|
||||||
},
|
},
|
||||||
request::{self, FromFormValue, FromRequest, Request},
|
request::{self, FromFormValue, FromRequest, Request},
|
||||||
response::{self, Flash, NamedFile, Redirect, Responder, Response},
|
response::{self, Flash, NamedFile, Redirect, Responder, Response},
|
||||||
@ -95,10 +94,11 @@ impl Page {
|
|||||||
#[derive(Shrinkwrap)]
|
#[derive(Shrinkwrap)]
|
||||||
pub struct ContentLen(pub u64);
|
pub struct ContentLen(pub u64);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for ContentLen {
|
impl<'a, 'r> FromRequest<'a, 'r> for ContentLen {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(r: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
async fn from_request(r: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
match r.limits().get("forms") {
|
match r.limits().get("forms") {
|
||||||
Some(l) => Outcome::Success(ContentLen(l)),
|
Some(l) => Outcome::Success(ContentLen(l)),
|
||||||
None => Outcome::Failure((Status::InternalServerError, ())),
|
None => Outcome::Failure((Status::InternalServerError, ())),
|
||||||
@ -208,14 +208,15 @@ pub mod well_known;
|
|||||||
#[response()]
|
#[response()]
|
||||||
pub struct CachedFile {
|
pub struct CachedFile {
|
||||||
inner: NamedFile,
|
inner: NamedFile,
|
||||||
cache_control: CacheControl,
|
cache_control: Header<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ThemeFile(NamedFile);
|
pub struct ThemeFile(NamedFile);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'r> Responder<'r> for ThemeFile {
|
impl<'r> Responder<'r> for ThemeFile {
|
||||||
fn respond_to(self, r: &Request<'_>) -> response::Result<'r> {
|
async fn respond_to(self, r: &'r Request<'_>) -> response::Result<'r> {
|
||||||
let contents = std::fs::read(self.0.path()).map_err(|_| Status::InternalServerError)?;
|
let contents = std::fs::read(self.0.path()).map_err(|_| Status::InternalServerError)?;
|
||||||
|
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
@ -228,12 +229,12 @@ impl<'r> Responder<'r> for ThemeFile {
|
|||||||
{
|
{
|
||||||
Response::build()
|
Response::build()
|
||||||
.status(Status::NotModified)
|
.status(Status::NotModified)
|
||||||
.header(ETag(EntityTag::strong(etag)))
|
.header(Header::new("ETag", etag))
|
||||||
.ok()
|
.ok()
|
||||||
} else {
|
} else {
|
||||||
Response::build()
|
Response::build()
|
||||||
.merge(self.0.respond_to(r)?)
|
.merge(self.0.respond_to(r).await.ok().unwrap())
|
||||||
.header(ETag(EntityTag::strong(etag)))
|
.header(Header::new("ETag", etag))
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,7 +257,7 @@ pub fn plume_media_files(file: PathBuf) -> Option<CachedFile> {
|
|||||||
.ok()
|
.ok()
|
||||||
.map(|f| CachedFile {
|
.map(|f| CachedFile {
|
||||||
inner: f,
|
inner: f,
|
||||||
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
cache_control: Header::new("Cache-Control", format!("max-age={}", 60 * 60 * 24 * 30)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#[get("/static/<file..>", rank = 3)]
|
#[get("/static/<file..>", rank = 3)]
|
||||||
@ -265,6 +266,6 @@ pub fn static_files(file: PathBuf) -> Option<CachedFile> {
|
|||||||
.ok()
|
.ok()
|
||||||
.map(|f| CachedFile {
|
.map(|f| CachedFile {
|
||||||
inner: f,
|
inner: f,
|
||||||
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
cache_control: Header::new("Cache-Control", format!("max-age={}", 60 * 60 * 24 * 30)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use activitypub::link;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use heck::{CamelCase, KebabCase};
|
use heck::{CamelCase, KebabCase};
|
||||||
use rocket::request::LenientForm;
|
use rocket::request::LenientForm;
|
||||||
@ -11,7 +12,7 @@ use std::{
|
|||||||
use validator::{Validate, ValidationError, ValidationErrors};
|
use validator::{Validate, ValidationError, ValidationErrors};
|
||||||
|
|
||||||
use crate::routes::{
|
use crate::routes::{
|
||||||
comments::NewCommentForm, errors::ErrorPage, ContentLen, RemoteForm, RespondOrRedirect,
|
comments::NewCommentForm, errors::ErrorPage, ContentLen, Page, RemoteForm, RespondOrRedirect,
|
||||||
};
|
};
|
||||||
use crate::template_utils::{IntoContext, Ructe};
|
use crate::template_utils::{IntoContext, Ructe};
|
||||||
use plume_common::activity_pub::{broadcast, ActivityStream, ApRequest};
|
use plume_common::activity_pub::{broadcast, ActivityStream, ApRequest};
|
||||||
@ -33,7 +34,7 @@ use plume_models::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[get("/~/<blog>/<slug>?<responding_to>", rank = 4)]
|
#[get("/~/<blog>/<slug>?<responding_to>", rank = 4)]
|
||||||
pub fn details(
|
pub async fn details(
|
||||||
blog: String,
|
blog: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
responding_to: Option<i32>,
|
responding_to: Option<i32>,
|
||||||
@ -41,7 +42,7 @@ pub fn details(
|
|||||||
) -> Result<Ructe, ErrorPage> {
|
) -> Result<Ructe, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let user = rockets.user.clone();
|
let user = rockets.user.clone();
|
||||||
let blog = Blog::find_by_fqn(&rockets, &blog)?;
|
let blog = Blog::find_by_fqn(&rockets, &blog).await?;
|
||||||
let post = Post::find_by_slug(&*conn, &slug, blog.id)?;
|
let post = Post::find_by_slug(&*conn, &slug, blog.id)?;
|
||||||
if !(post.published
|
if !(post.published
|
||||||
|| post
|
|| post
|
||||||
@ -99,14 +100,14 @@ pub fn details(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/~/<blog>/<slug>", rank = 3)]
|
#[get("/~/<blog>/<slug>", rank = 3)]
|
||||||
pub fn activity_details(
|
pub async fn activity_details(
|
||||||
blog: String,
|
blog: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
_ap: ApRequest,
|
_ap: ApRequest,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<ActivityStream<LicensedArticle>, Option<String>> {
|
) -> Result<ActivityStream<LicensedArticle>, Option<String>> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let blog = Blog::find_by_fqn(&rockets, &blog).map_err(|_| None)?;
|
let blog = Blog::find_by_fqn(&rockets, &blog).await.map_err(|_| None)?;
|
||||||
let post = Post::find_by_slug(&*conn, &slug, blog.id).map_err(|_| None)?;
|
let post = Post::find_by_slug(&*conn, &slug, blog.id).map_err(|_| None)?;
|
||||||
if post.published {
|
if post.published {
|
||||||
Ok(ActivityStream::new(
|
Ok(ActivityStream::new(
|
||||||
@ -130,9 +131,9 @@ pub fn new_auth(blog: String, i18n: I18n) -> Flash<Redirect> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/~/<blog>/new", rank = 1)]
|
#[get("/~/<blog>/new", rank = 1)]
|
||||||
pub fn new(blog: String, cl: ContentLen, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
pub async fn new(blog: String, cl: ContentLen, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let b = Blog::find_by_fqn(&rockets, &blog)?;
|
let b = Blog::find_by_fqn(&rockets, &blog).await?;
|
||||||
let user = rockets.user.clone().unwrap();
|
let user = rockets.user.clone().unwrap();
|
||||||
|
|
||||||
if !user.is_author_in(&*conn, &b)? {
|
if !user.is_author_in(&*conn, &b)? {
|
||||||
@ -162,7 +163,7 @@ pub fn new(blog: String, cl: ContentLen, rockets: PlumeRocket) -> Result<Ructe,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/~/<blog>/<slug>/edit")]
|
#[get("/~/<blog>/<slug>/edit")]
|
||||||
pub fn edit(
|
pub async fn edit(
|
||||||
blog: String,
|
blog: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
cl: ContentLen,
|
cl: ContentLen,
|
||||||
@ -170,7 +171,7 @@ pub fn edit(
|
|||||||
) -> Result<Ructe, ErrorPage> {
|
) -> Result<Ructe, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let intl = &rockets.intl.catalog;
|
let intl = &rockets.intl.catalog;
|
||||||
let b = Blog::find_by_fqn(&rockets, &blog)?;
|
let b = Blog::find_by_fqn(&rockets, &blog).await?;
|
||||||
let post = Post::find_by_slug(&*conn, &slug, b.id)?;
|
let post = Post::find_by_slug(&*conn, &slug, b.id)?;
|
||||||
let user = rockets.user.clone().unwrap();
|
let user = rockets.user.clone().unwrap();
|
||||||
|
|
||||||
@ -216,7 +217,7 @@ pub fn edit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/~/<blog>/<slug>/edit", data = "<form>")]
|
#[post("/~/<blog>/<slug>/edit", data = "<form>")]
|
||||||
pub fn update(
|
pub async fn update(
|
||||||
blog: String,
|
blog: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
cl: ContentLen,
|
cl: ContentLen,
|
||||||
@ -224,7 +225,9 @@ pub fn update(
|
|||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> RespondOrRedirect {
|
) -> RespondOrRedirect {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let b = Blog::find_by_fqn(&rockets, &blog).expect("post::update: blog error");
|
let b = Blog::find_by_fqn(&rockets, &blog)
|
||||||
|
.await
|
||||||
|
.expect("post::update: blog error");
|
||||||
let mut post =
|
let mut post =
|
||||||
Post::find_by_slug(&*conn, &slug, b.id).expect("post::update: find by slug error");
|
Post::find_by_slug(&*conn, &slug, b.id).expect("post::update: find by slug error");
|
||||||
let user = rockets.user.clone().unwrap();
|
let user = rockets.user.clone().unwrap();
|
||||||
@ -301,14 +304,16 @@ pub fn update(
|
|||||||
.expect("post::update: update error");
|
.expect("post::update: update error");
|
||||||
|
|
||||||
if post.published {
|
if post.published {
|
||||||
post.update_mentions(
|
// NOTE: here we unroll a filter_map(), so we can use .await painlessly
|
||||||
&conn,
|
let mut filtered_mentions: Vec<link::Mention> = vec![];
|
||||||
mentions
|
for m in mentions.into_iter() {
|
||||||
.into_iter()
|
match Mention::build_activity(&rockets, &m).await {
|
||||||
.filter_map(|m| Mention::build_activity(&rockets, &m).ok())
|
Ok(m) => filtered_mentions.push(m),
|
||||||
.collect(),
|
Err(_) => {}
|
||||||
)
|
}
|
||||||
.expect("post::update: mentions error");
|
}
|
||||||
|
post.update_mentions(&conn, filtered_mentions)
|
||||||
|
.expect("post::update: mentions error");
|
||||||
}
|
}
|
||||||
|
|
||||||
let tags = form
|
let tags = form
|
||||||
@ -321,7 +326,7 @@ pub fn update(
|
|||||||
.filter_map(|t| Tag::build_activity(t).ok())
|
.filter_map(|t| Tag::build_activity(t).ok())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
post.update_tags(&conn, tags)
|
post.update_tags(&conn, tags)
|
||||||
.expect("post::update: tags error");
|
.expect(r#"post::update: tags error"#);
|
||||||
|
|
||||||
let hashtags = hashtags
|
let hashtags = hashtags
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -399,14 +404,16 @@ pub fn valid_slug(title: &str) -> Result<(), ValidationError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/~/<blog_name>/new", data = "<form>")]
|
#[post("/~/<blog_name>/new", data = "<form>")]
|
||||||
pub fn create(
|
pub async fn create(
|
||||||
blog_name: String,
|
blog_name: String,
|
||||||
form: LenientForm<NewPostForm>,
|
form: LenientForm<NewPostForm>,
|
||||||
cl: ContentLen,
|
cl: ContentLen,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<RespondOrRedirect, ErrorPage> {
|
) -> Result<RespondOrRedirect, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let blog = Blog::find_by_fqn(&rockets, &blog_name).expect("post::create: blog error");
|
let blog = Blog::find_by_fqn(&rockets, &blog_name)
|
||||||
|
.await
|
||||||
|
.expect("post::create: blog error");
|
||||||
let slug = form.title.to_string().to_kebab_case();
|
let slug = form.title.to_string().to_kebab_case();
|
||||||
let user = rockets.user.clone().unwrap();
|
let user = rockets.user.clone().unwrap();
|
||||||
|
|
||||||
@ -520,6 +527,7 @@ pub fn create(
|
|||||||
Mention::from_activity(
|
Mention::from_activity(
|
||||||
&*conn,
|
&*conn,
|
||||||
&Mention::build_activity(&rockets, &m)
|
&Mention::build_activity(&rockets, &m)
|
||||||
|
.await
|
||||||
.expect("post::create: mention build error"),
|
.expect("post::create: mention build error"),
|
||||||
post.id,
|
post.id,
|
||||||
true,
|
true,
|
||||||
@ -562,7 +570,7 @@ pub fn create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/~/<blog_name>/<slug>/delete")]
|
#[post("/~/<blog_name>/<slug>/delete")]
|
||||||
pub fn delete(
|
pub async fn delete(
|
||||||
blog_name: String,
|
blog_name: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
@ -570,6 +578,7 @@ pub fn delete(
|
|||||||
) -> Result<Flash<Redirect>, ErrorPage> {
|
) -> Result<Flash<Redirect>, ErrorPage> {
|
||||||
let user = rockets.user.clone().unwrap();
|
let user = rockets.user.clone().unwrap();
|
||||||
let post = Blog::find_by_fqn(&rockets, &blog_name)
|
let post = Blog::find_by_fqn(&rockets, &blog_name)
|
||||||
|
.await
|
||||||
.and_then(|blog| Post::find_by_slug(&*rockets.conn, &slug, blog.id));
|
.and_then(|blog| Post::find_by_slug(&*rockets.conn, &slug, blog.id));
|
||||||
|
|
||||||
if let Ok(post) = post {
|
if let Ok(post) = post {
|
||||||
@ -615,12 +624,13 @@ pub fn delete(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/~/<blog_name>/<slug>/remote_interact")]
|
#[get("/~/<blog_name>/<slug>/remote_interact")]
|
||||||
pub fn remote_interact(
|
pub async fn remote_interact(
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
blog_name: String,
|
blog_name: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
) -> Result<Ructe, ErrorPage> {
|
) -> Result<Ructe, ErrorPage> {
|
||||||
let target = Blog::find_by_fqn(&rockets, &blog_name)
|
let target = Blog::find_by_fqn(&rockets, &blog_name)
|
||||||
|
.await
|
||||||
.and_then(|blog| Post::find_by_slug(&rockets.conn, &slug, blog.id))?;
|
.and_then(|blog| Post::find_by_slug(&rockets.conn, &slug, blog.id))?;
|
||||||
Ok(render!(posts::remote_interact(
|
Ok(render!(posts::remote_interact(
|
||||||
&rockets.to_context(),
|
&rockets.to_context(),
|
||||||
@ -633,17 +643,19 @@ pub fn remote_interact(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/~/<blog_name>/<slug>/remote_interact", data = "<remote>")]
|
#[post("/~/<blog_name>/<slug>/remote_interact", data = "<remote>")]
|
||||||
pub fn remote_interact_post(
|
pub async fn remote_interact_post(
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
blog_name: String,
|
blog_name: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
remote: LenientForm<RemoteForm>,
|
remote: LenientForm<RemoteForm>,
|
||||||
) -> Result<RespondOrRedirect, ErrorPage> {
|
) -> Result<RespondOrRedirect, ErrorPage> {
|
||||||
let target = Blog::find_by_fqn(&rockets, &blog_name)
|
let target = Blog::find_by_fqn(&rockets, &blog_name)
|
||||||
|
.await
|
||||||
.and_then(|blog| Post::find_by_slug(&rockets.conn, &slug, blog.id))?;
|
.and_then(|blog| Post::find_by_slug(&rockets.conn, &slug, blog.id))?;
|
||||||
if let Some(uri) = User::fetch_remote_interact_uri(&remote.remote)
|
if let uri = User::fetch_remote_interact_uri(&remote.remote)
|
||||||
.ok()
|
.await
|
||||||
.and_then(|uri| rt_format!(uri, uri = target.ap_url).ok())
|
.map(|uri| uri.replace("{uri}", &format!("{}", target.ap_url)))
|
||||||
|
.unwrap()
|
||||||
{
|
{
|
||||||
Ok(Redirect::to(uri).into())
|
Ok(Redirect::to(uri).into())
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,14 +10,14 @@ use plume_models::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[post("/~/<blog>/<slug>/reshare")]
|
#[post("/~/<blog>/<slug>/reshare")]
|
||||||
pub fn create(
|
pub async fn create(
|
||||||
blog: String,
|
blog: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
user: User,
|
user: User,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<Redirect, ErrorPage> {
|
) -> Result<Redirect, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let b = Blog::find_by_fqn(&rockets, &blog)?;
|
let b = Blog::find_by_fqn(&rockets, &blog).await?;
|
||||||
let post = Post::find_by_slug(&*conn, &slug, b.id)?;
|
let post = Post::find_by_slug(&*conn, &slug, b.id)?;
|
||||||
|
|
||||||
if !user.has_reshared(&*conn, &post)? {
|
if !user.has_reshared(&*conn, &post)? {
|
||||||
|
@ -35,21 +35,23 @@ pub fn new(m: Option<String>, rockets: PlumeRocket) -> Ructe {
|
|||||||
|
|
||||||
#[derive(Default, FromForm, Validate)]
|
#[derive(Default, FromForm, Validate)]
|
||||||
pub struct LoginForm {
|
pub struct LoginForm {
|
||||||
#[validate(length(min = "1", message = "We need an email, or a username to identify you"))]
|
#[validate(length(min = 1, message = "We need an email, or a username to identify you"))]
|
||||||
pub email_or_name: String,
|
pub email_or_name: String,
|
||||||
#[validate(length(min = "1", message = "Your password can't be empty"))]
|
#[validate(length(min = 1, message = "Your password can't be empty"))]
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/login", data = "<form>")]
|
#[post("/login", data = "<form>")]
|
||||||
pub fn create(
|
pub async fn create(
|
||||||
form: LenientForm<LoginForm>,
|
form: LenientForm<LoginForm>,
|
||||||
mut cookies: Cookies<'_>,
|
mut cookies: Cookies<'_>,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> RespondOrRedirect {
|
) -> RespondOrRedirect {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let user = User::find_by_email(&*conn, &form.email_or_name)
|
let user = match User::find_by_email(&*conn, &form.email_or_name) {
|
||||||
.or_else(|_| User::find_by_fqn(&rockets, &form.email_or_name));
|
Ok(user) => Ok(user),
|
||||||
|
Err(_) => User::find_by_fqn(&rockets, &form.email_or_name).await,
|
||||||
|
};
|
||||||
let mut errors = match form.validate() {
|
let mut errors = match form.validate() {
|
||||||
Ok(_) => ValidationErrors::new(),
|
Ok(_) => ValidationErrors::new(),
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
@ -197,7 +199,7 @@ pub fn password_reset_form(token: String, rockets: PlumeRocket) -> Result<Ructe,
|
|||||||
#[derive(FromForm, Default, Validate)]
|
#[derive(FromForm, Default, Validate)]
|
||||||
#[validate(schema(
|
#[validate(schema(
|
||||||
function = "passwords_match",
|
function = "passwords_match",
|
||||||
skip_on_field_errors = "false",
|
skip_on_field_errors = false,
|
||||||
message = "Passwords are not matching"
|
message = "Passwords are not matching"
|
||||||
))]
|
))]
|
||||||
pub struct NewPasswordForm {
|
pub struct NewPasswordForm {
|
||||||
|
@ -9,7 +9,6 @@ use rocket::{
|
|||||||
response::{status, Content, Flash, Redirect},
|
response::{status, Content, Flash, Redirect},
|
||||||
};
|
};
|
||||||
use rocket_i18n::I18n;
|
use rocket_i18n::I18n;
|
||||||
use serde_json;
|
|
||||||
use std::{borrow::Cow, collections::HashMap};
|
use std::{borrow::Cow, collections::HashMap};
|
||||||
use validator::{Validate, ValidationError, ValidationErrors};
|
use validator::{Validate, ValidationError, ValidationErrors};
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ pub fn me(user: Option<User>) -> RespondOrRedirect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>", rank = 2)]
|
#[get("/@/<name>", rank = 2)]
|
||||||
pub fn details(
|
pub async fn details(
|
||||||
name: String,
|
name: String,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
fetch_rockets: PlumeRocket,
|
fetch_rockets: PlumeRocket,
|
||||||
@ -50,7 +49,7 @@ pub fn details(
|
|||||||
update_conn: DbConn,
|
update_conn: DbConn,
|
||||||
) -> Result<Ructe, ErrorPage> {
|
) -> Result<Ructe, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let user = User::find_by_fqn(&rockets, &name)?;
|
let user = User::find_by_fqn(&rockets, &name).await?;
|
||||||
let recents = Post::get_recents_for_author(&*conn, &user, 6)?;
|
let recents = Post::get_recents_for_author(&*conn, &user, 6)?;
|
||||||
let reshares = Reshare::get_recents_for_author(&*conn, &user, 6)?;
|
let reshares = Reshare::get_recents_for_author(&*conn, &user, 6)?;
|
||||||
let worker = &rockets.worker;
|
let worker = &rockets.worker;
|
||||||
@ -61,6 +60,7 @@ pub fn details(
|
|||||||
worker.execute(move || {
|
worker.execute(move || {
|
||||||
for create_act in user_clone
|
for create_act in user_clone
|
||||||
.fetch_outbox::<Create>()
|
.fetch_outbox::<Create>()
|
||||||
|
.await
|
||||||
.expect("Remote user: outbox couldn't be fetched")
|
.expect("Remote user: outbox couldn't be fetched")
|
||||||
{
|
{
|
||||||
match create_act.create_props.object_object::<LicensedArticle>() {
|
match create_act.create_props.object_object::<LicensedArticle>() {
|
||||||
@ -79,6 +79,7 @@ pub fn details(
|
|||||||
worker.execute(move || {
|
worker.execute(move || {
|
||||||
for user_id in user_clone
|
for user_id in user_clone
|
||||||
.fetch_followers_ids()
|
.fetch_followers_ids()
|
||||||
|
.await
|
||||||
.expect("Remote user: fetching followers error")
|
.expect("Remote user: fetching followers error")
|
||||||
{
|
{
|
||||||
let follower = User::from_id(&fetch_followers_rockets, &user_id, None)
|
let follower = User::from_id(&fetch_followers_rockets, &user_id, None)
|
||||||
@ -101,6 +102,7 @@ pub fn details(
|
|||||||
worker.execute(move || {
|
worker.execute(move || {
|
||||||
user_clone
|
user_clone
|
||||||
.refetch(&*update_conn)
|
.refetch(&*update_conn)
|
||||||
|
.await
|
||||||
.expect("Couldn't update user info");
|
.expect("Couldn't update user info");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -146,13 +148,13 @@ pub fn dashboard_auth(i18n: I18n) -> Flash<Redirect> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/@/<name>/follow")]
|
#[post("/@/<name>/follow")]
|
||||||
pub fn follow(
|
pub async fn follow(
|
||||||
name: String,
|
name: String,
|
||||||
user: User,
|
user: User,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<Flash<Redirect>, ErrorPage> {
|
) -> Result<Flash<Redirect>, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let target = User::find_by_fqn(&rockets, &name)?;
|
let target = User::find_by_fqn(&rockets, &name).await?;
|
||||||
let message = if let Ok(follow) = follows::Follow::find(&*conn, user.id, target.id) {
|
let message = if let Ok(follow) = follows::Follow::find(&*conn, user.id, target.id) {
|
||||||
let delete_act = follow.build_undo(&*conn)?;
|
let delete_act = follow.build_undo(&*conn)?;
|
||||||
local_inbox(
|
local_inbox(
|
||||||
@ -190,26 +192,26 @@ pub fn follow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/@/<name>/follow", data = "<remote_form>", rank = 2)]
|
#[post("/@/<name>/follow", data = "<remote_form>", rank = 2)]
|
||||||
pub fn follow_not_connected(
|
pub async fn follow_not_connected(
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
name: String,
|
name: String,
|
||||||
remote_form: Option<LenientForm<RemoteForm>>,
|
remote_form: Option<LenientForm<RemoteForm>>,
|
||||||
i18n: I18n,
|
i18n: I18n,
|
||||||
) -> Result<RespondOrRedirect, ErrorPage> {
|
) -> Result<RespondOrRedirect, ErrorPage> {
|
||||||
let target = User::find_by_fqn(&rockets, &name)?;
|
let target = User::find_by_fqn(&rockets, &name).await?;
|
||||||
if let Some(remote_form) = remote_form {
|
if let Some(remote_form) = remote_form {
|
||||||
if let Some(uri) = User::fetch_remote_interact_uri(&remote_form)
|
if let Some(uri) = User::fetch_remote_interact_uri(&remote_form)
|
||||||
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|uri| {
|
.and_then(|uri| {
|
||||||
rt_format!(
|
Some(uri.replace(
|
||||||
uri,
|
"{uri}",
|
||||||
uri = format!(
|
&format!(
|
||||||
"{}@{}",
|
"{}@{}",
|
||||||
target.fqn,
|
target.fqn,
|
||||||
target.get_instance(&rockets.conn).ok()?.public_domain
|
target.get_instance(&rockets.conn).ok()?.public_domain
|
||||||
)
|
),
|
||||||
)
|
))
|
||||||
.ok()
|
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Ok(Redirect::to(uri).into())
|
Ok(Redirect::to(uri).into())
|
||||||
@ -266,15 +268,18 @@ pub fn follow_auth(name: String, i18n: I18n) -> Flash<Redirect> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>/followers?<page>", rank = 2)]
|
#[get("/@/<name>/followers?<page>", rank = 2)]
|
||||||
pub fn followers(
|
pub async fn followers(
|
||||||
name: String,
|
name: String,
|
||||||
page: Option<Page>,
|
page: Option<Page>,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<Ructe, ErrorPage> {
|
) -> Result<Ructe, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let page = page.unwrap_or_default();
|
let page = page.unwrap_or_default();
|
||||||
let user = User::find_by_fqn(&rockets, &name)?;
|
let user: User = User::find_by_fqn(&rockets, &name).await?;
|
||||||
let followers_count = user.count_followers(&*conn)?;
|
let followers_count = match user.count_followers(&conn) {
|
||||||
|
Ok(num) => num,
|
||||||
|
Err(_) => 0,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(render!(users::followers(
|
Ok(render!(users::followers(
|
||||||
&rockets.to_context(),
|
&rockets.to_context(),
|
||||||
@ -293,14 +298,14 @@ pub fn followers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>/followed?<page>", rank = 2)]
|
#[get("/@/<name>/followed?<page>", rank = 2)]
|
||||||
pub fn followed(
|
pub async fn followed(
|
||||||
name: String,
|
name: String,
|
||||||
page: Option<Page>,
|
page: Option<Page>,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<Ructe, ErrorPage> {
|
) -> Result<Ructe, ErrorPage> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let page = page.unwrap_or_default();
|
let page = page.unwrap_or_default();
|
||||||
let user = User::find_by_fqn(&rockets, &name)?;
|
let user = User::find_by_fqn(&rockets, &name).await?;
|
||||||
let followed_count = user.count_followed(conn)?;
|
let followed_count = user.count_followed(conn)?;
|
||||||
|
|
||||||
Ok(render!(users::followed(
|
Ok(render!(users::followed(
|
||||||
@ -320,12 +325,12 @@ pub fn followed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>", rank = 1)]
|
#[get("/@/<name>", rank = 1)]
|
||||||
pub fn activity_details(
|
pub async fn activity_details(
|
||||||
name: String,
|
name: String,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
_ap: ApRequest,
|
_ap: ApRequest,
|
||||||
) -> Option<ActivityStream<CustomPerson>> {
|
) -> Option<ActivityStream<CustomPerson>> {
|
||||||
let user = User::find_by_fqn(&rockets, &name).ok()?;
|
let user: User = User::find_by_fqn(&rockets, &name).await?;
|
||||||
Some(ActivityStream::new(user.to_activity(&*rockets.conn).ok()?))
|
Some(ActivityStream::new(user.to_activity(&*rockets.conn).ok()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,50 +417,49 @@ pub fn update(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/@/<name>/delete")]
|
#[post("/@/<name>/delete")]
|
||||||
pub fn delete(
|
pub async fn delete(
|
||||||
name: String,
|
name: String,
|
||||||
user: User,
|
user: User,
|
||||||
mut cookies: Cookies<'_>,
|
mut cookies: Cookies<'_>,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<Flash<Redirect>, ErrorPage> {
|
) -> Result<Flash<Redirect>, ErrorPage> {
|
||||||
let account = User::find_by_fqn(&rockets, &name)?;
|
let account = User::find_by_fqn(&rockets, &name).await?;
|
||||||
if user.id == account.id {
|
if user.id != account.id {
|
||||||
account.delete(&*rockets.conn, &rockets.searcher)?;
|
return Ok(Flash::error(
|
||||||
|
|
||||||
let target = User::one_by_instance(&*rockets.conn)?;
|
|
||||||
let delete_act = account.delete_activity(&*rockets.conn)?;
|
|
||||||
rockets
|
|
||||||
.worker
|
|
||||||
.execute(move || broadcast(&account, delete_act, target));
|
|
||||||
|
|
||||||
if let Some(cookie) = cookies.get_private(AUTH_COOKIE) {
|
|
||||||
cookies.remove_private(cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Flash::success(
|
|
||||||
Redirect::to(uri!(super::instance::index)),
|
|
||||||
i18n!(rockets.intl.catalog, "Your account has been deleted."),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(Flash::error(
|
|
||||||
Redirect::to(uri!(edit: name = name)),
|
Redirect::to(uri!(edit: name = name)),
|
||||||
i18n!(
|
i18n!(
|
||||||
rockets.intl.catalog,
|
rockets.intl.catalog,
|
||||||
"You can't delete someone else's account."
|
"You can't delete someone else's account."
|
||||||
),
|
),
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
|
account.delete(&*rockets.conn, &rockets.searcher)?;
|
||||||
|
|
||||||
|
let target = User::one_by_instance(&*rockets.conn)?;
|
||||||
|
let delete_act = account.delete_activity(&*rockets.conn)?;
|
||||||
|
rockets
|
||||||
|
.worker
|
||||||
|
.execute(move || broadcast(&account, delete_act, target));
|
||||||
|
|
||||||
|
if let Some(cookie) = cookies.get_private(AUTH_COOKIE) {
|
||||||
|
cookies.remove_private(cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Flash::success(
|
||||||
|
Redirect::to(uri!(super::instance::index)),
|
||||||
|
i18n!(rockets.intl.catalog, "Your account has been deleted."),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, FromForm, Validate)]
|
#[derive(Default, FromForm, Validate)]
|
||||||
#[validate(schema(
|
#[validate(schema(
|
||||||
function = "passwords_match",
|
function = "passwords_match",
|
||||||
skip_on_field_errors = "false",
|
skip_on_field_errors = false,
|
||||||
message = "Passwords are not matching"
|
message = "Passwords are not matching"
|
||||||
))]
|
))]
|
||||||
pub struct NewUserForm {
|
pub struct NewUserForm {
|
||||||
#[validate(
|
#[validate(
|
||||||
length(min = "1", message = "Username can't be empty"),
|
length(min = 1, message = "Username can't be empty"),
|
||||||
custom(
|
custom(
|
||||||
function = "validate_username",
|
function = "validate_username",
|
||||||
message = "User name is not allowed to contain any of < > & @ ' or \""
|
message = "User name is not allowed to contain any of < > & @ ' or \""
|
||||||
@ -464,9 +468,9 @@ pub struct NewUserForm {
|
|||||||
pub username: String,
|
pub username: String,
|
||||||
#[validate(email(message = "Invalid email"))]
|
#[validate(email(message = "Invalid email"))]
|
||||||
pub email: String,
|
pub email: String,
|
||||||
#[validate(length(min = "8", message = "Password should be at least 8 characters long"))]
|
#[validate(length(min = 8, message = "Password should be at least 8 characters long"))]
|
||||||
pub password: String,
|
pub password: String,
|
||||||
#[validate(length(min = "8", message = "Password should be at least 8 characters long"))]
|
#[validate(length(min = 8, message = "Password should be at least 8 characters long"))]
|
||||||
pub password_confirmation: String,
|
pub password_confirmation: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,37 +569,44 @@ pub fn create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>/outbox")]
|
#[get("/@/<name>/outbox")]
|
||||||
pub fn outbox(name: String, rockets: PlumeRocket) -> Option<ActivityStream<OrderedCollection>> {
|
pub async fn outbox(
|
||||||
let user = User::find_by_fqn(&rockets, &name).ok()?;
|
name: String,
|
||||||
|
rockets: PlumeRocket,
|
||||||
|
) -> Option<ActivityStream<OrderedCollection>> {
|
||||||
|
let user = User::find_by_fqn(&rockets, &name).await.ok()?;
|
||||||
user.outbox(&*rockets.conn).ok()
|
user.outbox(&*rockets.conn).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>/outbox?<page>")]
|
#[get("/@/<name>/outbox?<page>")]
|
||||||
pub fn outbox_page(
|
pub async fn outbox_page(
|
||||||
name: String,
|
name: String,
|
||||||
page: Page,
|
page: Page,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Option<ActivityStream<OrderedCollectionPage>> {
|
) -> Option<ActivityStream<OrderedCollectionPage>> {
|
||||||
let user = User::find_by_fqn(&rockets, &name).ok()?;
|
let user = User::find_by_fqn(&rockets, &name).await.ok()?;
|
||||||
user.outbox_page(&*rockets.conn, page.limits()).ok()
|
user.outbox_page(&*rockets.conn, page.limits()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/@/<name>/inbox", data = "<data>")]
|
#[post("/@/<name>/inbox", data = "<data>")]
|
||||||
pub fn inbox(
|
pub async fn inbox(
|
||||||
name: String,
|
name: String,
|
||||||
data: inbox::SignedJson<serde_json::Value>,
|
data: inbox::SignedJson<serde_json::Value>,
|
||||||
headers: Headers<'_>,
|
headers: Headers<'_>,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<String, status::BadRequest<&'static str>> {
|
) -> Result<String, status::BadRequest<&'static str>> {
|
||||||
User::find_by_fqn(&rockets, &name).map_err(|_| status::BadRequest(Some("User not found")))?;
|
User::find_by_fqn(&rockets, &name)
|
||||||
inbox::handle_incoming(rockets, data, headers)
|
.await
|
||||||
|
.map_err(|_| status::BadRequest(Some("User not found")))?;
|
||||||
|
inbox::handle_incoming(rockets, data, headers).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>/followers", rank = 1)]
|
#[get("/@/<name>/followers", rank = 1)]
|
||||||
pub fn ap_followers(
|
pub async fn ap_followers(
|
||||||
name: String,
|
name: String,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
_ap: ApRequest,
|
_ap: ApRequest,
|
||||||
) -> Option<ActivityStream<OrderedCollection>> {
|
) -> Option<ActivityStream<OrderedCollection>> {
|
||||||
let user = User::find_by_fqn(&rockets, &name).ok()?;
|
let user = User::find_by_fqn(&rockets, &name).await?;
|
||||||
let followers = user
|
let followers = user
|
||||||
.get_followers(&*rockets.conn)
|
.get_followers(&*rockets.conn)
|
||||||
.ok()?
|
.ok()?
|
||||||
@ -615,9 +626,9 @@ pub fn ap_followers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>/atom.xml")]
|
#[get("/@/<name>/atom.xml")]
|
||||||
pub fn atom_feed(name: String, rockets: PlumeRocket) -> Option<Content<String>> {
|
pub async fn atom_feed(name: String, rockets: PlumeRocket) -> Option<Content<String>> {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let author = User::find_by_fqn(&rockets, &name).ok()?;
|
let author = User::find_by_fqn(&rockets, &name).await?;
|
||||||
let entries = Post::get_recents_for_author(conn, &author, 15).ok()?;
|
let entries = Post::get_recents_for_author(conn, &author, 15).ok()?;
|
||||||
let uri = Instance::get_local()
|
let uri = Instance::get_local()
|
||||||
.ok()?
|
.ok()?
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use rocket::http::ContentType;
|
use rocket::http::ContentType;
|
||||||
use rocket::response::Content;
|
use rocket::response::Content;
|
||||||
use serde_json;
|
|
||||||
use webfinger::*;
|
use webfinger::*;
|
||||||
|
|
||||||
use plume_models::{ap_url, blogs::Blog, users::User, PlumeRocket, CONFIG};
|
use plume_models::{ap_url, blogs::Blog, users::User, PlumeRocket, CONFIG};
|
||||||
@ -43,27 +42,58 @@ pub fn host_meta() -> String {
|
|||||||
|
|
||||||
struct WebfingerResolver;
|
struct WebfingerResolver;
|
||||||
|
|
||||||
impl Resolver<PlumeRocket> for WebfingerResolver {
|
#[async_trait::async_trait]
|
||||||
fn instance_domain<'a>() -> &'a str {
|
impl AsyncResolver for WebfingerResolver {
|
||||||
|
type Repo = PlumeRocket;
|
||||||
|
async fn instance_domain<'a>(&self) -> &'a str {
|
||||||
CONFIG.base_url.as_str()
|
CONFIG.base_url.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find(prefix: Prefix, acct: String, ctx: PlumeRocket) -> Result<Webfinger, ResolverError> {
|
async fn find(
|
||||||
|
&self,
|
||||||
|
prefix: Prefix,
|
||||||
|
acct: String,
|
||||||
|
ctx: PlumeRocket,
|
||||||
|
) -> Result<Webfinger, ResolverError> {
|
||||||
match prefix {
|
match prefix {
|
||||||
Prefix::Acct => User::find_by_fqn(&ctx, &acct)
|
Prefix::Acct => User::find_by_fqn(&ctx, &acct)
|
||||||
|
.await
|
||||||
.and_then(|usr| usr.webfinger(&*ctx.conn))
|
.and_then(|usr| usr.webfinger(&*ctx.conn))
|
||||||
.or(Err(ResolverError::NotFound)),
|
.or(Err(ResolverError::NotFound)),
|
||||||
Prefix::Group => Blog::find_by_fqn(&ctx, &acct)
|
Prefix::Group => Blog::find_by_fqn(&ctx, &acct)
|
||||||
|
.await
|
||||||
.and_then(|blog| blog.webfinger(&*ctx.conn))
|
.and_then(|blog| blog.webfinger(&*ctx.conn))
|
||||||
.or(Err(ResolverError::NotFound)),
|
.or(Err(ResolverError::NotFound)),
|
||||||
Prefix::Custom(_) => Err(ResolverError::NotFound),
|
Prefix::Custom(_) => Err(ResolverError::NotFound),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async fn endpoint<R: Into<String> + Send>(
|
||||||
|
&self,
|
||||||
|
resource: R,
|
||||||
|
resource_repo: PlumeRocket,
|
||||||
|
) -> Result<Webfinger, ResolverError> {
|
||||||
|
let resource = resource.into();
|
||||||
|
let mut parsed_query = resource.splitn(2, ':');
|
||||||
|
let res_prefix = Prefix::from(parsed_query.next().ok_or(ResolverError::InvalidResource)?);
|
||||||
|
let res = parsed_query.next().ok_or(ResolverError::InvalidResource)?;
|
||||||
|
|
||||||
|
let mut parsed_res = res.splitn(2, '@');
|
||||||
|
let user = parsed_res.next().ok_or(ResolverError::InvalidResource)?;
|
||||||
|
let domain = parsed_res.next().ok_or(ResolverError::InvalidResource)?;
|
||||||
|
if domain == self.instance_domain().await {
|
||||||
|
self.find(res_prefix, user.to_string(), resource_repo).await
|
||||||
|
} else {
|
||||||
|
Err(ResolverError::WrongDomain)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/.well-known/webfinger?<resource>")]
|
#[get("/.well-known/webfinger?<resource>")]
|
||||||
pub fn webfinger(resource: String, rockets: PlumeRocket) -> Content<String> {
|
pub async fn webfinger(resource: String, rockets: PlumeRocket) -> Content<String> {
|
||||||
match WebfingerResolver::endpoint(resource, rockets)
|
let wf_resolver = WebfingerResolver;
|
||||||
|
match wf_resolver
|
||||||
|
.endpoint(resource, rockets)
|
||||||
|
.await
|
||||||
.and_then(|wf| serde_json::to_string(&wf).map_err(|_| ResolverError::NotFound))
|
.and_then(|wf| serde_json::to_string(&wf).map_err(|_| ResolverError::NotFound))
|
||||||
{
|
{
|
||||||
Ok(wf) => Content(ContentType::new("application", "jrd+json"), wf),
|
Ok(wf) => Content(ContentType::new("application", "jrd+json"), wf),
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use plume_models::{notifications::*, users::User, Connection, PlumeRocket};
|
use plume_models::{notifications::*, users::User, Connection, PlumeRocket};
|
||||||
|
|
||||||
use crate::templates::Html;
|
use crate::templates::Html;
|
||||||
use rocket::http::hyper::header::{ETag, EntityTag};
|
use rocket::http::{Header, Method, Status};
|
||||||
use rocket::http::{Method, Status};
|
|
||||||
use rocket::request::Request;
|
use rocket::request::Request;
|
||||||
use rocket::response::{self, content::Html as HtmlCt, Responder, Response};
|
use rocket::response::{self, content::Html as HtmlCt, Responder, Response};
|
||||||
use rocket_i18n::Catalog;
|
use rocket_i18n::Catalog;
|
||||||
@ -52,11 +51,12 @@ impl IntoContext for PlumeRocket {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Ructe(pub Vec<u8>);
|
pub struct Ructe(pub Vec<u8>);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<'r> Responder<'r> for Ructe {
|
impl<'r> Responder<'r> for Ructe {
|
||||||
fn respond_to(self, r: &Request<'_>) -> response::Result<'r> {
|
async fn respond_to(self, r: &'r Request<'_>) -> response::Result<'r> {
|
||||||
//if method is not Get or page contain a form, no caching
|
//if method is not Get or page contain a form, no caching
|
||||||
if r.method() != Method::Get || self.0.windows(6).any(|w| w == b"<form ") {
|
if r.method() != Method::Get || self.0.windows(6).any(|w| w == b"<form ") {
|
||||||
return HtmlCt(self.0).respond_to(r);
|
return HtmlCt(self.0).respond_to(r).await;
|
||||||
}
|
}
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
hasher.write(&self.0);
|
hasher.write(&self.0);
|
||||||
@ -67,12 +67,12 @@ impl<'r> Responder<'r> for Ructe {
|
|||||||
{
|
{
|
||||||
Response::build()
|
Response::build()
|
||||||
.status(Status::NotModified)
|
.status(Status::NotModified)
|
||||||
.header(ETag(EntityTag::strong(etag)))
|
.header(Header::new("ETag", etag))
|
||||||
.ok()
|
.ok()
|
||||||
} else {
|
} else {
|
||||||
Response::build()
|
Response::build()
|
||||||
.merge(HtmlCt(self.0).respond_to(r)?)
|
.merge(HtmlCt(self.0).respond_to(r).await.ok().unwrap())
|
||||||
.header(ETag(EntityTag::strong(etag)))
|
.header(Header::new("ETag", etag))
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
@use plume_models::CONFIG;
|
@use plume_models::CONFIG;
|
||||||
@use plume_models::instance::Instance;
|
@use plume_models::instance::Instance;
|
||||||
|
@use rocket::http::RawStr;
|
||||||
|
@use rocket::request::Form;
|
||||||
@use std::path::Path;
|
@use std::path::Path;
|
||||||
|
@use std::path::PathBuf;
|
||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
@use crate::routes::*;
|
@use crate::routes::*;
|
||||||
|
@use crate::routes::search::SearchQuery;
|
||||||
|
|
||||||
@(ctx: BaseContext, title: String, head: Content, header: Content, content: Content)
|
@(ctx: BaseContext, title: String, head: Content, header: Content, content: Content)
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
@use plume_models::instance::Instance;
|
@use plume_models::instance::Instance;
|
||||||
@use plume_models::posts::Post;
|
@use plume_models::posts::Post;
|
||||||
@use plume_models::users::User;
|
@use plume_models::users::User;
|
||||||
|
@use rocket::http::RawStr;
|
||||||
@use std::path::Path;
|
@use std::path::Path;
|
||||||
|
@use std::path::PathBuf;
|
||||||
@use crate::templates::{base, partials::post_card};
|
@use crate::templates::{base, partials::post_card};
|
||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
@use crate::routes::*;
|
@use crate::routes::*;
|
||||||
|
@ -5,10 +5,12 @@
|
|||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
@use crate::templates::base;
|
@use crate::templates::base;
|
||||||
@use crate::templates::partials::image_select;
|
@use crate::templates::partials::image_select;
|
||||||
|
@use crate::routes::Page;
|
||||||
@use crate::routes::blogs;
|
@use crate::routes::blogs;
|
||||||
@use crate::routes::blogs::EditForm;
|
@use crate::routes::blogs::EditForm;
|
||||||
@use crate::routes::medias;
|
@use crate::routes::medias;
|
||||||
|
|
||||||
|
|
||||||
@(ctx: BaseContext, blog: &Blog, medias: Vec<Media>, form: &EditForm, errors: ValidationErrors)
|
@(ctx: BaseContext, blog: &Blog, medias: Vec<Media>, form: &EditForm, errors: ValidationErrors)
|
||||||
|
|
||||||
@:base(ctx, i18n!(ctx.1, "Edit \"{}\""; &blog.title), {}, {
|
@:base(ctx, i18n!(ctx.1, "Edit \"{}\""; &blog.title), {}, {
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
@use plume_models::posts::Post;
|
@use plume_models::posts::Post;
|
||||||
@use plume_models::tags::Tag;
|
@use plume_models::tags::Tag;
|
||||||
@use plume_models::users::User;
|
@use plume_models::users::User;
|
||||||
|
@use rocket::http::RawStr;
|
||||||
@use std::path::Path;
|
@use std::path::Path;
|
||||||
|
@use std::path::PathBuf;
|
||||||
@use validator::ValidationErrors;
|
@use validator::ValidationErrors;
|
||||||
@use crate::templates::{base, partials::comment};
|
@use crate::templates::{base, partials::comment};
|
||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user