Merge branch 'warp-0.2' into 'master'
Migrate to warp 0.2 Closes #3 See merge request pastebinrun/pastebinrun!86
This commit is contained in:
commit
b3ca2bcb00
1154
Cargo.lock
generated
1154
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
|
@ -13,23 +13,23 @@ chrono = "0.4.10"
|
|||
diesel = { version = "1.4.3", features = ["chrono", "postgres", "r2d2"] }
|
||||
diesel_migrations = "1.4.0"
|
||||
env_logger = { version = "0.7.1", default-features = false }
|
||||
futures = "0.1.29"
|
||||
futures03 = { version = "0.3.1", package = "futures", features = ["compat"] }
|
||||
extension-trait = "0.2.1"
|
||||
futures = "0.3.1"
|
||||
itertools = "0.8.2"
|
||||
log = "0.4.8"
|
||||
mime = "0.3.16"
|
||||
once_cell = "1.3.1"
|
||||
pulldown-cmark = "0.6.1"
|
||||
rand = "0.7.3"
|
||||
reqwest = "0.9.24"
|
||||
reqwest = { version = "0.10.1", features = ["json"] }
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.47"
|
||||
time-parse = "0.1.2"
|
||||
tokio-executor = { version = "=0.2.0-alpha.6", features = ["blocking"] }
|
||||
warp = "0.1.20"
|
||||
tokio = { version = "0.2.4", features = ["blocking", "macros"] }
|
||||
warp = "0.2.1"
|
||||
|
||||
[build-dependencies]
|
||||
ructe = { version = "0.9.2", features = ["mime03", "warp"] }
|
||||
ructe = "0.9.2"
|
||||
|
||||
[dev-dependencies]
|
||||
scraper = "0.11.0"
|
||||
|
|
27
src/blocking.rs
Normal file
27
src/blocking.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2020 Konrad Borowski
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use tokio::task;
|
||||
use warp::Rejection;
|
||||
|
||||
pub async fn run<R>(
|
||||
f: impl FnOnce() -> Result<R, Rejection> + Send + 'static,
|
||||
) -> Result<R, Rejection>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
task::spawn_blocking(f).await.unwrap()
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
#[macro_use]
|
||||
extern crate diesel;
|
||||
|
||||
mod blocking;
|
||||
mod migration;
|
||||
mod models;
|
||||
mod routes;
|
||||
|
@ -31,14 +32,17 @@ use std::error::Error;
|
|||
|
||||
type Connection = PooledConnection<ConnectionManager<PgConnection>>;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
env_logger::init();
|
||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL required");
|
||||
let pool = Pool::new(ConnectionManager::new(database_url))
|
||||
.expect("Couldn't create a connection connection");
|
||||
diesel_migrations::run_pending_migrations(&pool.get()?)?;
|
||||
migration::run(&pool.get()?)?;
|
||||
warp::serve(routes::routes(pool)).run(([0, 0, 0, 0], 8080));
|
||||
warp::serve(routes::routes(pool))
|
||||
.run(([127, 0, 0, 1], 8080))
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
31
src/models/db.rs
Normal file
31
src/models/db.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2020 Konrad Borowski
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use extension_trait::extension_trait;
|
||||
use warp::reject::{Reject, Rejection};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DbError(diesel::result::Error);
|
||||
|
||||
impl Reject for DbError {}
|
||||
|
||||
#[extension_trait(pub)]
|
||||
impl<T> DbErrorExt for Result<T, diesel::result::Error> {
|
||||
type Error = T;
|
||||
fn into_rejection(self) -> Result<Self::Error, Rejection> {
|
||||
self.map_err(|e| warp::reject::custom(DbError(e)))
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::models::db::DbErrorExt;
|
||||
use crate::schema::languages::dsl::*;
|
||||
use crate::Connection;
|
||||
use diesel::prelude::*;
|
||||
|
@ -32,7 +33,7 @@ impl Language {
|
|||
.select((language_id, identifier, name))
|
||||
.order((priority.asc(), name.asc()))
|
||||
.load(connection)
|
||||
.map_err(warp::reject::custom)
|
||||
.into_rejection()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod db;
|
||||
pub mod js;
|
||||
pub mod language;
|
||||
pub mod paste;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::models::db::DbErrorExt;
|
||||
use crate::models::rejection::CustomRejection;
|
||||
use crate::schema::{languages, pastes};
|
||||
use crate::Connection;
|
||||
|
@ -45,7 +46,7 @@ impl Paste {
|
|||
let pastes = diesel::delete(pastes::table)
|
||||
.filter(pastes::delete_at.lt(Utc::now()))
|
||||
.execute(connection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
.into_rejection()?;
|
||||
if pastes > 0 {
|
||||
info!("Deleted {} paste(s)", pastes);
|
||||
}
|
||||
|
@ -97,7 +98,7 @@ pub fn insert(
|
|||
.filter(languages::identifier.eq(language))
|
||||
.get_result(connection)
|
||||
.optional()
|
||||
.map_err(warp::reject::custom)?
|
||||
.into_rejection()?
|
||||
.ok_or_else(|| warp::reject::custom(CustomRejection::UnrecognizedLanguageIdentifier))?;
|
||||
for (field, name) in &[(&paste, "paste"), (&stdin, "stdin")] {
|
||||
if field.len() > 1_000_000 {
|
||||
|
@ -124,7 +125,7 @@ pub fn insert(
|
|||
diesel::insert_into(pastes::table)
|
||||
.values(&insert_paste)
|
||||
.execute(connection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
.into_rejection()?;
|
||||
Ok(insert_paste.identifier)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
use warp::http::StatusCode;
|
||||
use warp::reject::Reject;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CustomRejection {
|
||||
|
@ -42,4 +42,4 @@ impl Display for CustomRejection {
|
|||
}
|
||||
}
|
||||
|
||||
impl Error for CustomRejection {}
|
||||
impl Reject for CustomRejection {}
|
||||
|
|
|
@ -15,9 +15,14 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::Connection;
|
||||
use extension_trait::extension_trait;
|
||||
use mime::TEXT_HTML_UTF_8;
|
||||
use std::borrow::Cow;
|
||||
use warp::http::header::CONTENT_SECURITY_POLICY;
|
||||
use std::io;
|
||||
use warp::http;
|
||||
use warp::http::header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE};
|
||||
use warp::http::response::{Builder, Response};
|
||||
use warp::reject::{Reject, Rejection};
|
||||
|
||||
pub struct Session {
|
||||
pub nonce: String,
|
||||
|
@ -27,8 +32,7 @@ pub struct Session {
|
|||
|
||||
impl Session {
|
||||
pub fn render(&self) -> Builder {
|
||||
let mut builder = Response::builder();
|
||||
builder.header(
|
||||
Response::builder().header(
|
||||
CONTENT_SECURITY_POLICY,
|
||||
format!(
|
||||
concat!(
|
||||
|
@ -44,7 +48,30 @@ impl Session {
|
|||
),
|
||||
nonce = self.nonce,
|
||||
),
|
||||
);
|
||||
builder
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[extension_trait(pub)]
|
||||
impl RenderExt for Builder {
|
||||
fn html<F>(self, f: F) -> Result<Response<Vec<u8>>, Rejection>
|
||||
where
|
||||
F: FnOnce(&mut Vec<u8>) -> io::Result<()>,
|
||||
{
|
||||
let mut buf = Vec::new();
|
||||
f(&mut buf).map_err(|e| warp::reject::custom(TemplateError(e)))?;
|
||||
self.header(CONTENT_TYPE, TEXT_HTML_UTF_8.as_ref())
|
||||
.body(buf)
|
||||
.map_err(|e| warp::reject::custom(RenderError(e)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TemplateError(io::Error);
|
||||
|
||||
impl Reject for TemplateError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RenderError(http::Error);
|
||||
|
||||
impl Reject for RenderError {}
|
||||
|
|
|
@ -14,13 +14,11 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::models::db::DbErrorExt;
|
||||
use crate::schema::{implementation_wrappers, implementations, languages};
|
||||
use crate::Connection;
|
||||
use crate::{blocking, Connection};
|
||||
use diesel::prelude::*;
|
||||
use futures::Future;
|
||||
use futures03::prelude::*;
|
||||
use serde::Serialize;
|
||||
use tokio_executor::blocking;
|
||||
use warp::http::header::CACHE_CONTROL;
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
|
@ -69,24 +67,24 @@ struct JsonImplementation {
|
|||
wrappers: Vec<Wrapper>,
|
||||
}
|
||||
|
||||
pub fn api_language(
|
||||
pub async fn api_language(
|
||||
connection: Connection,
|
||||
identifier: String,
|
||||
) -> impl Future<Item = impl Reply, Error = Rejection> {
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
blocking::run(move || {
|
||||
let language: Language = languages::table
|
||||
.filter(languages::identifier.eq(identifier))
|
||||
.select((languages::language_id, languages::hello_world))
|
||||
.get_result(&connection)
|
||||
.optional()
|
||||
.map_err(warp::reject::custom)?
|
||||
.into_rejection()?
|
||||
.ok_or_else(warp::reject::not_found)?;
|
||||
let implementations = implementations::table
|
||||
.select((implementations::implementation_id, implementations::label))
|
||||
.filter(implementations::language_id.eq(language.id))
|
||||
.order(implementations::ordering)
|
||||
.load(&connection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
.into_rejection()?;
|
||||
let implementation_wrappers = ImplementationWrapper::belonging_to(&implementations)
|
||||
.select((
|
||||
implementation_wrappers::implementation_wrapper_id,
|
||||
|
@ -98,7 +96,7 @@ pub fn api_language(
|
|||
))
|
||||
.order(implementation_wrappers::ordering)
|
||||
.load(&connection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
.into_rejection()?;
|
||||
let implementations = implementation_wrappers
|
||||
.grouped_by(&implementations)
|
||||
.into_iter()
|
||||
|
@ -135,5 +133,5 @@ pub fn api_language(
|
|||
"max-age=14400",
|
||||
))
|
||||
})
|
||||
.compat()
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -14,13 +14,11 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::models::db::DbErrorExt;
|
||||
use crate::schema::languages;
|
||||
use crate::Connection;
|
||||
use crate::{blocking, Connection};
|
||||
use diesel::prelude::*;
|
||||
use futures::Future;
|
||||
use futures03::TryFutureExt;
|
||||
use serde::Serialize;
|
||||
use tokio_executor::blocking;
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
#[derive(Queryable, Serialize)]
|
||||
|
@ -29,13 +27,13 @@ struct Language {
|
|||
name: String,
|
||||
}
|
||||
|
||||
pub fn languages(connection: Connection) -> impl Future<Item = impl Reply, Error = Rejection> {
|
||||
pub async fn languages(connection: Connection) -> Result<impl Reply, Rejection> {
|
||||
blocking::run(move || {
|
||||
let languages: Vec<Language> = languages::table
|
||||
.select((languages::identifier, languages::name))
|
||||
.load(&connection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
.into_rejection()?;
|
||||
Ok(warp::reply::json(&languages))
|
||||
})
|
||||
.compat()
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -15,14 +15,11 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::models::paste::{self, ExtraPasteParameters};
|
||||
use crate::Connection;
|
||||
use crate::{blocking, Connection};
|
||||
use chrono::{Duration, Utc};
|
||||
use futures::Future;
|
||||
use futures03::TryFutureExt;
|
||||
use serde::de::{Deserializer, Unexpected, Visitor};
|
||||
use serde::{de, Deserialize};
|
||||
use std::fmt::{self, Formatter};
|
||||
use tokio_executor::blocking;
|
||||
use warp::Rejection;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -71,14 +68,14 @@ fn default_language() -> String {
|
|||
"plaintext".into()
|
||||
}
|
||||
|
||||
pub fn insert_paste(
|
||||
pub async fn insert_paste(
|
||||
PasteForm {
|
||||
expiration,
|
||||
language,
|
||||
code,
|
||||
}: PasteForm,
|
||||
connection: Connection,
|
||||
) -> impl Future<Item = String, Error = Rejection> {
|
||||
) -> Result<String, Rejection> {
|
||||
blocking::run(move || {
|
||||
paste::insert(
|
||||
&connection,
|
||||
|
@ -93,5 +90,5 @@ pub fn insert_paste(
|
|||
},
|
||||
)
|
||||
})
|
||||
.compat()
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::models::session::Session;
|
||||
use crate::templates::{self, RenderRucte};
|
||||
use crate::models::session::{RenderExt, Session};
|
||||
use crate::templates;
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
pub fn config(session: Session) -> Result<impl Reply, Rejection> {
|
||||
pub async fn config(session: Session) -> Result<impl Reply, Rejection> {
|
||||
session.render().html(|o| templates::config(o, &session))
|
||||
}
|
||||
|
|
|
@ -14,22 +14,21 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::blocking;
|
||||
use crate::models::db::DbErrorExt;
|
||||
use crate::models::language::{Language, Selection};
|
||||
use crate::models::paste::{ExternPaste, Paste};
|
||||
use crate::models::session::Session;
|
||||
use crate::models::session::{RenderExt, Session};
|
||||
use crate::schema::{languages, pastes};
|
||||
use crate::templates::{self, RenderRucte};
|
||||
use crate::templates;
|
||||
use diesel::prelude::*;
|
||||
use futures::future::*;
|
||||
use futures03::TryFutureExt;
|
||||
use std::borrow::Cow;
|
||||
use tokio_executor::blocking;
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
pub fn display_paste(
|
||||
pub async fn display_paste(
|
||||
requested_identifier: String,
|
||||
mut session: Session,
|
||||
) -> impl Future<Item = impl Reply, Error = Rejection> {
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
blocking::run(move || {
|
||||
let connection = &session.connection;
|
||||
Paste::delete_old(connection)?;
|
||||
|
@ -49,7 +48,7 @@ pub fn display_paste(
|
|||
.filter(pastes::identifier.eq(requested_identifier))
|
||||
.get_result(connection)
|
||||
.optional()
|
||||
.map_err(warp::reject::custom)?
|
||||
.into_rejection()?
|
||||
.ok_or_else(warp::reject::not_found)?;
|
||||
session.description = generate_description(&paste.paste);
|
||||
let selected_language = Some(paste.language_id);
|
||||
|
@ -65,7 +64,7 @@ pub fn display_paste(
|
|||
)
|
||||
})
|
||||
})
|
||||
.compat()
|
||||
.await
|
||||
}
|
||||
|
||||
fn generate_description(paste: &str) -> Cow<'static, str> {
|
||||
|
|
|
@ -14,15 +14,13 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::blocking;
|
||||
use crate::models::language::{Language, Selection};
|
||||
use crate::models::session::Session;
|
||||
use crate::templates::{self, RenderRucte};
|
||||
use futures::Future;
|
||||
use futures03::TryFutureExt;
|
||||
use tokio_executor::blocking;
|
||||
use crate::models::session::{RenderExt, Session};
|
||||
use crate::templates;
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
pub fn index(session: Session) -> impl Future<Item = impl Reply, Error = Rejection> {
|
||||
pub async fn index(session: Session) -> Result<impl Reply, Rejection> {
|
||||
blocking::run(move || {
|
||||
let languages = Language::fetch(&session.connection)?;
|
||||
session.render().html(|o| {
|
||||
|
@ -36,5 +34,5 @@ pub fn index(session: Session) -> impl Future<Item = impl Reply, Error = Rejecti
|
|||
)
|
||||
})
|
||||
})
|
||||
.compat()
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -16,12 +16,9 @@
|
|||
|
||||
use crate::models::paste;
|
||||
use crate::models::paste::ExtraPasteParameters;
|
||||
use crate::Connection;
|
||||
use crate::{blocking, Connection};
|
||||
use chrono::{Duration, Utc};
|
||||
use futures::Future;
|
||||
use futures03::TryFutureExt;
|
||||
use serde::Deserialize;
|
||||
use tokio_executor::blocking;
|
||||
use warp::http::header::LOCATION;
|
||||
use warp::http::StatusCode;
|
||||
use warp::{reply, Rejection, Reply};
|
||||
|
@ -45,7 +42,7 @@ pub enum Share {
|
|||
Share24,
|
||||
}
|
||||
|
||||
pub fn insert_paste(
|
||||
pub async fn insert_paste(
|
||||
PasteForm {
|
||||
language,
|
||||
code,
|
||||
|
@ -56,7 +53,7 @@ pub fn insert_paste(
|
|||
status,
|
||||
}: PasteForm,
|
||||
connection: Connection,
|
||||
) -> impl Future<Item = impl Reply, Error = Rejection> {
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
blocking::run(move || {
|
||||
let delete_at = match share {
|
||||
Share::Share => None,
|
||||
|
@ -80,5 +77,5 @@ pub fn insert_paste(
|
|||
format!("/{}", identifier),
|
||||
))
|
||||
})
|
||||
.compat()
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -24,128 +24,131 @@ mod raw_paste;
|
|||
mod run;
|
||||
|
||||
use crate::models::rejection::CustomRejection;
|
||||
use crate::models::session::Session;
|
||||
use crate::templates::{self, RenderRucte};
|
||||
use crate::Connection;
|
||||
use crate::models::session::{RenderExt, Session};
|
||||
use crate::templates;
|
||||
use crate::{blocking, Connection};
|
||||
use diesel::prelude::*;
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use futures03::compat::Compat;
|
||||
use futures03::{Future, FutureExt, TryFutureExt};
|
||||
use futures::{Future, FutureExt};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use tokio_executor::blocking;
|
||||
use warp::filters::cors::Cors;
|
||||
use warp::filters::BoxedFilter;
|
||||
use warp::http::header::{
|
||||
HeaderMap, HeaderValue, CONTENT_SECURITY_POLICY, CONTENT_TYPE, REFERRER_POLICY, X_FRAME_OPTIONS,
|
||||
HeaderMap, HeaderValue, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
CONTENT_SECURITY_POLICY, CONTENT_TYPE, REFERRER_POLICY, X_FRAME_OPTIONS,
|
||||
};
|
||||
use warp::http::method::Method;
|
||||
use warp::http::{Response, StatusCode};
|
||||
use warp::reject::Reject;
|
||||
use warp::{path, Filter, Rejection, Reply};
|
||||
|
||||
type PgPool = Pool<ConnectionManager<PgConnection>>;
|
||||
|
||||
fn connection(pool: PgPool) -> BoxedFilter<(Connection,)> {
|
||||
warp::any()
|
||||
.and_then(move || get_connection(pool.clone()).compat())
|
||||
.and_then(move || get_connection(pool.clone()))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn get_connection(pool: PgPool) -> impl Future<Output = Result<Connection, Rejection>> {
|
||||
blocking::run(move || pool.get().map_err(warp::reject::custom))
|
||||
async fn get_connection(pool: PgPool) -> Result<Connection, Rejection> {
|
||||
blocking::run(move || {
|
||||
pool.get()
|
||||
.map_err(|e| warp::reject::custom(ConnectionError(e)))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ConnectionError<E>(E);
|
||||
|
||||
impl<E: 'static + std::fmt::Debug + Send + Sync> Reject for ConnectionError<E> {}
|
||||
|
||||
fn session(pool: PgPool) -> BoxedFilter<(Session,)> {
|
||||
warp::any()
|
||||
.and_then(move || get_session(pool.clone()).compat())
|
||||
.and_then(move || get_session(pool.clone()))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn get_session(pool: PgPool) -> impl Future<Output = Result<Session, Rejection>> {
|
||||
get_connection(pool).map_ok(|connection| {
|
||||
async fn get_session(pool: PgPool) -> Result<Session, Rejection> {
|
||||
let connection = get_connection(pool).await?;
|
||||
let bytes: [u8; 32] = rand::random();
|
||||
Session {
|
||||
Ok(Session {
|
||||
nonce: base64::encode(&bytes),
|
||||
connection,
|
||||
description: "Compile and share code in multiple programming languages".into(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn index(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||
warp::path::end()
|
||||
.and(
|
||||
warp::post2()
|
||||
warp::post()
|
||||
.and(warp::body::form())
|
||||
.and(connection(pool.clone()))
|
||||
.and_then(insert_paste::insert_paste)
|
||||
.or(warp::get2().and(session(pool)).and_then(index::index)),
|
||||
.or(warp::get().and(session(pool)).and_then(index::index)),
|
||||
)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn display_paste(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::get2())
|
||||
warp::path!(String)
|
||||
.and(warp::get())
|
||||
.and(session(pool))
|
||||
.and_then(display_paste::display_paste)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn options(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||
warp::path("config")
|
||||
.and(warp::path::end())
|
||||
.and(warp::get2())
|
||||
warp::path!("config")
|
||||
.and(warp::get())
|
||||
.and(session(pool))
|
||||
.and_then(config::config)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn raw_paste(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||
with_ext("txt")
|
||||
.map(warp::ext::set)
|
||||
.untuple_one()
|
||||
.and(
|
||||
warp::get2()
|
||||
.and(warp::ext::get())
|
||||
warp::get()
|
||||
.and(with_ext("txt"))
|
||||
.and(connection(pool))
|
||||
.and_then(raw_paste::raw_paste)
|
||||
.with(cors()),
|
||||
)
|
||||
.or(warp::options().and(with_ext("txt")).map(|_| {
|
||||
Response::builder()
|
||||
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
|
||||
.header(ACCESS_CONTROL_ALLOW_METHODS, "GET")
|
||||
.body("")
|
||||
}))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn api_v0(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||
let root = path!("api" / "v0").and(connection(pool));
|
||||
let root = path!("api" / "v0" / ..).and(connection(pool));
|
||||
let language = root
|
||||
.clone()
|
||||
.and(path!("language" / String))
|
||||
.and(warp::path::end())
|
||||
.and(warp::get2())
|
||||
.and(warp::get())
|
||||
.and_then(api_language::api_language);
|
||||
let run = root
|
||||
.and(path!("run" / String))
|
||||
.and(warp::post2())
|
||||
.and(warp::post())
|
||||
.and(warp::body::form())
|
||||
.and_then(run::run);
|
||||
language.or(run).boxed()
|
||||
}
|
||||
|
||||
fn api_v1(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||
let languages = warp::path("languages")
|
||||
.and(warp::path::end())
|
||||
.and(warp::get2())
|
||||
let languages = warp::path!("languages")
|
||||
.and(warp::get())
|
||||
.and(connection(pool.clone()))
|
||||
.and_then(api_v1::languages::languages);
|
||||
let pastes = warp::path("pastes")
|
||||
.and(warp::path::end())
|
||||
.and(warp::post2())
|
||||
let pastes = warp::path!("pastes")
|
||||
.and(warp::post())
|
||||
.and(warp::body::form())
|
||||
.and(connection(pool))
|
||||
.and_then(api_v1::pastes::insert_paste);
|
||||
path!("api" / "v1")
|
||||
path!("api" / "v1" / ..)
|
||||
.and(languages.or(pastes).with(cors()))
|
||||
.boxed()
|
||||
}
|
||||
|
@ -155,6 +158,7 @@ fn cors() -> Cors {
|
|||
.allow_any_origin()
|
||||
.allow_methods(&[Method::GET, Method::POST])
|
||||
.allow_headers(&[CONTENT_TYPE])
|
||||
.build()
|
||||
}
|
||||
|
||||
fn static_dir() -> BoxedFilter<(impl Reply,)> {
|
||||
|
@ -162,15 +166,14 @@ fn static_dir() -> BoxedFilter<(impl Reply,)> {
|
|||
}
|
||||
|
||||
fn favicon() -> BoxedFilter<(impl Reply,)> {
|
||||
warp::path("favicon.ico")
|
||||
.and(warp::path::end())
|
||||
warp::path!("favicon.ico")
|
||||
.and(warp::fs::file("static/favicon.ico"))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
pub fn routes(
|
||||
pool: Pool<ConnectionManager<PgConnection>>,
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> {
|
||||
) -> impl Clone + Filter<Extract = (impl Reply,), Error = Rejection> {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
|
||||
headers.insert(REFERRER_POLICY, HeaderValue::from_static("no-referrer"));
|
||||
|
@ -192,9 +195,7 @@ pub fn routes(
|
|||
}
|
||||
|
||||
fn with_ext(ext: &'static str) -> impl Filter<Extract = (String,), Error = Rejection> + Copy {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and_then(move |path: PathBuf| {
|
||||
warp::path!(PathBuf).and_then(move |path: PathBuf| async move {
|
||||
match (path.extension(), path.file_stem().and_then(OsStr::to_str)) {
|
||||
(Some(received_ext), Some(file_stem)) if ext == received_ext => {
|
||||
Ok(file_stem.to_string())
|
||||
|
@ -204,18 +205,19 @@ fn with_ext(ext: &'static str) -> impl Filter<Extract = (String,), Error = Rejec
|
|||
})
|
||||
}
|
||||
|
||||
type NotFoundFuture =
|
||||
Compat<Pin<Box<dyn Future<Output = Result<Response<Vec<u8>>, Rejection>> + Send>>>;
|
||||
|
||||
fn not_found(pool: PgPool) -> impl Clone + Fn(Rejection) -> NotFoundFuture {
|
||||
fn not_found(
|
||||
pool: PgPool,
|
||||
) -> impl Clone
|
||||
+ Fn(Rejection) -> Pin<Box<dyn Future<Output = Result<Response<Vec<u8>>, Rejection>> + Send>>
|
||||
{
|
||||
move |rejection| {
|
||||
let pool = pool.clone();
|
||||
async move {
|
||||
if let Some(rejection) = rejection.find_cause::<CustomRejection>() {
|
||||
Response::builder()
|
||||
if let Some(rejection) = rejection.find::<CustomRejection>() {
|
||||
Ok(Response::builder()
|
||||
.status(rejection.status_code())
|
||||
.body(rejection.to_string().into_bytes())
|
||||
.map_err(warp::reject::custom)
|
||||
.unwrap())
|
||||
} else if rejection.is_not_found() {
|
||||
let session = get_session(pool.clone()).await?;
|
||||
session
|
||||
|
@ -227,7 +229,6 @@ fn not_found(pool: PgPool) -> impl Clone + Fn(Rejection) -> NotFoundFuture {
|
|||
}
|
||||
}
|
||||
.boxed()
|
||||
.compat()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,9 +275,9 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_sh_id() -> String {
|
||||
async fn get_sh_id() -> String {
|
||||
let response = warp::test::request().reply(&*ROUTES);
|
||||
let document = Html::parse_document(str::from_utf8(response.body()).unwrap());
|
||||
let document = Html::parse_document(str::from_utf8(response.await.body()).unwrap());
|
||||
document
|
||||
.select(&Selector::parse("#language option").unwrap())
|
||||
.find(|element| element.text().next() == Some("Sh"))
|
||||
|
@ -303,17 +304,18 @@ mod test {
|
|||
is_formatter: bool,
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(not(feature = "database_tests"), ignore)]
|
||||
fn test_language_api() {
|
||||
async fn test_language_api() {
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
pub struct ApiLanguage<'a> {
|
||||
#[serde(borrow)]
|
||||
implementations: Vec<Implementation<'a>>,
|
||||
}
|
||||
let response = warp::test::request()
|
||||
.path(&format!("/api/v0/language/{}", get_sh_id()))
|
||||
.reply(&*ROUTES);
|
||||
.path(&format!("/api/v0/language/{}", get_sh_id().await))
|
||||
.reply(&*ROUTES)
|
||||
.await;
|
||||
assert_eq!(
|
||||
serde_json::from_slice::<ApiLanguage>(response.body()).unwrap(),
|
||||
ApiLanguage {
|
||||
|
@ -330,29 +332,31 @@ mod test {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(not(feature = "database_tests"), ignore)]
|
||||
fn test_raw_pastes() {
|
||||
let body = format!("language={}&code=abc&share=share24", get_sh_id());
|
||||
async fn test_raw_pastes() {
|
||||
let body = format!("language={}&code=abc&share=share24", get_sh_id().await);
|
||||
let reply = warp::test::request()
|
||||
.method("POST")
|
||||
.header(CONTENT_LENGTH, body.len())
|
||||
.body(body)
|
||||
.reply(&*ROUTES);
|
||||
.reply(&*ROUTES)
|
||||
.await;
|
||||
assert_eq!(reply.status(), StatusCode::SEE_OTHER);
|
||||
let location = reply.headers()[LOCATION].to_str().unwrap();
|
||||
assert_eq!(
|
||||
warp::test::request()
|
||||
.path(&format!("{}.txt", location))
|
||||
.reply(&*ROUTES)
|
||||
.await
|
||||
.body(),
|
||||
"abc"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(not(feature = "sandbox_tests"), ignore)]
|
||||
fn test_sandbox() {
|
||||
async fn test_sandbox() {
|
||||
#[derive(Deserialize)]
|
||||
struct LanguageIdentifier<'a> {
|
||||
identifier: &'a str,
|
||||
|
@ -366,13 +370,15 @@ mod test {
|
|||
}
|
||||
let languages = warp::test::request()
|
||||
.path("/api/v1/languages")
|
||||
.reply(&*ROUTES);
|
||||
.reply(&*ROUTES)
|
||||
.await;
|
||||
let languages =
|
||||
serde_json::from_slice::<Vec<LanguageIdentifier>>(languages.body()).unwrap();
|
||||
for LanguageIdentifier { identifier } in languages {
|
||||
let language = warp::test::request()
|
||||
.path(&format!("/api/v0/language/{}", identifier))
|
||||
.reply(&*ROUTES);
|
||||
.reply(&*ROUTES)
|
||||
.await;
|
||||
let ApiLanguage {
|
||||
hello_world,
|
||||
implementations,
|
||||
|
@ -388,7 +394,8 @@ mod test {
|
|||
.method("POST")
|
||||
.header(CONTENT_LENGTH, body.len())
|
||||
.body(body)
|
||||
.reply(&*ROUTES);
|
||||
.reply(&*ROUTES)
|
||||
.await;
|
||||
let body = str::from_utf8(out.body()).unwrap();
|
||||
assert!(
|
||||
body.contains(r#"Hello, world!\n""#),
|
||||
|
@ -400,9 +407,9 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(not(feature = "database_tests"), ignore)]
|
||||
fn raw_cors() {
|
||||
async fn raw_cors() {
|
||||
assert_eq!(
|
||||
warp::test::request()
|
||||
.path("/a.txt")
|
||||
|
@ -410,14 +417,15 @@ mod test {
|
|||
.header("origin", "example.com")
|
||||
.header("access-control-request-method", "GET")
|
||||
.reply(&*ROUTES)
|
||||
.await
|
||||
.status(),
|
||||
StatusCode::OK,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(not(feature = "database_tests"), ignore)]
|
||||
fn paste_no_cors() {
|
||||
async fn paste_no_cors() {
|
||||
assert_eq!(
|
||||
warp::test::request()
|
||||
.path("/a")
|
||||
|
@ -425,14 +433,15 @@ mod test {
|
|||
.header("origin", "example.com")
|
||||
.header("access-control-request-method", "GET")
|
||||
.reply(&*ROUTES)
|
||||
.await
|
||||
.status(),
|
||||
StatusCode::METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(not(feature = "database_tests"), ignore)]
|
||||
fn api_v1_cors() {
|
||||
async fn api_v1_cors() {
|
||||
assert_eq!(
|
||||
warp::test::request()
|
||||
.path("/api/v1/languages")
|
||||
|
@ -440,6 +449,7 @@ mod test {
|
|||
.header("origin", "example.com")
|
||||
.header("access-control-request-method", "GET")
|
||||
.reply(&*ROUTES)
|
||||
.await
|
||||
.status(),
|
||||
StatusCode::OK,
|
||||
);
|
||||
|
|
|
@ -14,19 +14,17 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::models::db::DbErrorExt;
|
||||
use crate::models::paste::Paste;
|
||||
use crate::schema::pastes::dsl::*;
|
||||
use crate::Connection;
|
||||
use crate::{blocking, Connection};
|
||||
use diesel::prelude::*;
|
||||
use futures::Future;
|
||||
use futures03::TryFutureExt;
|
||||
use tokio_executor::blocking;
|
||||
use warp::Rejection;
|
||||
|
||||
pub fn raw_paste(
|
||||
pub async fn raw_paste(
|
||||
requested_identifier: String,
|
||||
connection: Connection,
|
||||
) -> impl Future<Item = String, Error = Rejection> {
|
||||
) -> Result<String, Rejection> {
|
||||
blocking::run(move || {
|
||||
Paste::delete_old(&connection)?;
|
||||
pastes
|
||||
|
@ -34,8 +32,8 @@ pub fn raw_paste(
|
|||
.filter(identifier.eq(requested_identifier))
|
||||
.get_result(&connection)
|
||||
.optional()
|
||||
.map_err(warp::reject::custom)?
|
||||
.into_rejection()?
|
||||
.ok_or_else(warp::reject::not_found)
|
||||
})
|
||||
.compat()
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -14,16 +14,16 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::models::db::DbErrorExt;
|
||||
use crate::schema::implementation_wrappers;
|
||||
use crate::Connection;
|
||||
use crate::{blocking, Connection};
|
||||
use diesel::prelude::*;
|
||||
use futures::Future;
|
||||
use futures03::TryFutureExt;
|
||||
use futures::TryFutureExt;
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::r#async::Client;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use tokio_executor::blocking;
|
||||
use warp::reject::Reject;
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
static CLIENT: Lazy<Client> = Lazy::new(Client::new);
|
||||
|
@ -57,7 +57,7 @@ struct Output {
|
|||
stderr: String,
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
pub async fn run(
|
||||
connection: Connection,
|
||||
identifier: String,
|
||||
Form {
|
||||
|
@ -65,19 +65,18 @@ pub fn run(
|
|||
compiler_options,
|
||||
stdin,
|
||||
}: Form,
|
||||
) -> impl Future<Item = impl Reply, Error = Rejection> {
|
||||
blocking::run(move || {
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let language_code: String = blocking::run(move || {
|
||||
implementation_wrappers::table
|
||||
.filter(implementation_wrappers::identifier.eq(identifier))
|
||||
.select(implementation_wrappers::code)
|
||||
.get_result(&connection)
|
||||
.optional()
|
||||
.map_err(warp::reject::custom)?
|
||||
.into_rejection()?
|
||||
.ok_or_else(warp::reject::not_found)
|
||||
})
|
||||
.compat()
|
||||
.and_then(move |language_code: String| {
|
||||
CLIENT
|
||||
.await?;
|
||||
let json: Output = CLIENT
|
||||
.post(SANDBOX_URL.as_str())
|
||||
.json(&Request {
|
||||
files: vec![File {
|
||||
|
@ -88,8 +87,13 @@ pub fn run(
|
|||
code: language_code.replace("%s", &compiler_options),
|
||||
})
|
||||
.send()
|
||||
.and_then(|mut r| r.json())
|
||||
.map_err(warp::reject::custom)
|
||||
})
|
||||
.map(|json: Output| warp::reply::json(&json))
|
||||
.and_then(|r| r.json())
|
||||
.map_err(|e| warp::reject::custom(RemoteServerError(e)))
|
||||
.await?;
|
||||
Ok(warp::reply::json(&json))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RemoteServerError(reqwest::Error);
|
||||
|
||||
impl Reject for RemoteServerError {}
|
||||
|
|
Loading…
Reference in New Issue
Block a user