recieve webmentions

This commit is contained in:
Cadey Ratio 2020-11-04 12:18:17 -05:00
parent 90b9d53bf8
commit 9bbfcf74c8
8 changed files with 137 additions and 7 deletions

1
backend/Cargo.lock generated
View File

@ -1033,6 +1033,7 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
"twapi-ureq", "twapi-ureq",
"ureq", "ureq",
"url 2.1.1",
"uuid", "uuid",
] ]

View File

@ -9,27 +9,28 @@ edition = "2018"
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
color-eyre = "0.5" color-eyre = "0.5"
twapi-ureq = "0.1.5" futures-io = "0.3"
hex = "0.4"
kankyo = "0.3" kankyo = "0.3"
log = "0.4" log = "0.4"
mime = "0.3.0" mime = "0.3.0"
paseto = { version = "1.0", features = ["easy_tokens", "v2"] } paseto = { version = "1.0", features = ["easy_tokens", "v2"] }
ring = { version = "^0.16", features = ["std"] } prometheus = { version = "0.10", default-features = false, features = ["process"] }
rand = "0" rand = "0"
ring = { version = "^0.16", features = ["std"] }
rocket = "0.4"
rocket_prometheus = "0.7.0"
rusty_ulid = "0.10" rusty_ulid = "0.10"
serde_json = "^1" serde_json = "^1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
thiserror = "1" thiserror = "1"
rocket = "0.4"
tracing = "0.1" tracing = "0.1"
tracing-log = "0.1" tracing-log = "0.1"
tracing-subscriber = "0.2" tracing-subscriber = "0.2"
twapi-ureq = "0.1.5"
ureq = { version = "1", features = ["json", "charset"] } ureq = { version = "1", features = ["json", "charset"] }
uuid = { version = "0.7", features = ["serde", "v4"] } uuid = { version = "0.7", features = ["serde", "v4"] }
rocket_prometheus = "0.7.0" url = "2"
prometheus = { version = "0.10", default-features = false, features = ["process"] }
futures-io = "0.3"
hex = "0.4"
jsonfeed = { git = "https://github.com/Xe/site" } jsonfeed = { git = "https://github.com/Xe/site" }

View File

@ -0,0 +1 @@
DROP INDEX webmentions_source_target;

View File

@ -0,0 +1,2 @@
CREATE UNIQUE INDEX webmentions_source_target
ON webmentions(source_url, target_url);

View File

@ -13,6 +13,7 @@ use rocket_contrib::json::Json;
use std::io::Read; use std::io::Read;
pub mod switch; pub mod switch;
pub mod webmention;
#[get("/members")] #[get("/members")]
#[instrument(skip(conn), err)] #[instrument(skip(conn), err)]
@ -79,6 +80,12 @@ pub enum Error {
#[error("web API interop error: {0}")] #[error("web API interop error: {0}")]
Web(#[from] web::Error), Web(#[from] web::Error),
#[error("URL parsing error: {0}")]
URL(#[from] url::ParseError),
#[error("invalid webmention: {0}")]
InvalidWebMention(String),
} }
pub type Result<T = ()> = std::result::Result<T, Error>; pub type Result<T = ()> = std::result::Result<T, Error>;
@ -88,6 +95,7 @@ impl<'a> Responder<'a> for Error {
error!("{}", self); error!("{}", self);
match self { match self {
Error::NotFound => Err(Status::NotFound), Error::NotFound => Err(Status::NotFound),
Error::InvalidWebMention(_) => Err(Status::BadRequest),
_ => Err(Status::InternalServerError), _ => Err(Status::InternalServerError),
} }
} }

View File

@ -0,0 +1,107 @@
use super::{Error, Result};
use crate::{models, schema, MainDatabase};
use diesel::prelude::*;
use rocket::{
request::Form,
response::{self, Responder},
Request, Response,
};
use rocket_contrib::json::Json;
use rusty_ulid::generate_ulid_string;
use url::Url;
#[derive(FromForm, Debug)]
pub struct WebMention {
source: String,
target: String,
}
impl WebMention {
fn check(&self) -> Result {
if self.source == self.target {
return Err(Error::InvalidWebMention("source == target".into()));
}
let u: Url = Url::parse(&self.source)?;
match u.scheme() {
"http" | "https" => {}
_ => return Err(Error::InvalidWebMention("invalid source scheme".into())),
}
u.host_str()
.ok_or(Error::InvalidWebMention("no host found in target".into()))?;
let u: Url = Url::parse(&self.target)?;
match u.scheme() {
"http" | "https" => {}
_ => return Err(Error::InvalidWebMention("invalid target scheme".into())),
}
match u
.host_str()
.ok_or(Error::InvalidWebMention("no host found in target".into()))?
{
"christine.website" | "cetacean.club" => {}
_ => return Err(Error::InvalidWebMention("invalid target host".into())),
}
Ok(())
}
}
impl Into<models::WebMention> for WebMention {
fn into(self) -> models::WebMention {
models::WebMention {
id: generate_ulid_string(),
source_url: self.source,
target_url: self.target,
}
}
}
impl<'a> Responder<'a> for models::WebMention {
fn respond_to(self, _: &Request) -> response::Result<'a> {
Response::build()
.raw_header(
"Location",
format!("https://mi.christine.website/api/webmention/{}", self.id),
)
.ok()
}
}
#[post("/webmention/accept", data = "<mention>")]
#[instrument(skip(conn, mention), err)]
pub fn accept(conn: MainDatabase, mention: Form<WebMention>) -> Result<models::WebMention> {
use schema::webmentions;
let mention = mention.into_inner();
mention.check()?;
info!(
source = &mention.source[..],
target = &mention.target[..],
"webmention received"
);
let wm: models::WebMention = mention.into();
diesel::insert_into(webmentions::table)
.values(&wm)
.execute(&*conn)
.map_err(Error::Database)?;
Ok(wm)
}
#[get("/webmention/<mention_id>")]
#[instrument(skip(conn), err)]
pub fn get(conn: MainDatabase, mention_id: String) -> Result<Json<models::WebMention>> {
use schema::webmentions::dsl::webmentions;
Ok(Json(
webmentions
.find(mention_id)
.get_result(&*conn)
.map_err(Error::Database)?,
))
}

View File

@ -57,6 +57,8 @@ fn main() -> Result<()> {
api::switch::get, api::switch::get,
api::switch::list, api::switch::list,
api::switch::switch, api::switch::switch,
api::webmention::accept,
api::webmention::get,
api::get_members, api::get_members,
api::token_info, api::token_info,
api::tweet, api::tweet,

View File

@ -50,3 +50,11 @@ pub struct NewSwitch {
pub struct UpdateSwitchTime { pub struct UpdateSwitchTime {
pub ended_at: Option<NaiveDateTime>, pub ended_at: Option<NaiveDateTime>,
} }
#[derive(Queryable, Associations, Insertable, Serialize)]
#[table_name = "webmentions"]
pub struct WebMention {
pub id: String,
pub source_url: String,
pub target_url: String,
}