Merge branch 'api-v1-pastes' into 'master'
Implement v1 API for pastes See merge request pastebin.run/server!66
This commit is contained in:
commit
1706b78a00
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -151,6 +151,7 @@ dependencies = [
|
|||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ build = "src/build.rs"
|
|||
[dependencies]
|
||||
ammonia = "3.0.0"
|
||||
base64 = "0.10.1"
|
||||
chrono = "0.4.6"
|
||||
chrono = { version = "0.4.9", features = ["serde"] }
|
||||
diesel = { version = "1.4.1", features = ["chrono", "postgres", "r2d2"] }
|
||||
diesel_migrations = "1.4.0"
|
||||
env_logger = { version = "0.6.0", default-features = false }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod language;
|
||||
pub mod paste;
|
||||
pub mod rejection;
|
||||
pub mod session;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::schema::pastes;
|
||||
use crate::models::rejection::CustomRejection;
|
||||
use crate::schema::{languages, pastes};
|
||||
use crate::Connection;
|
||||
use ammonia::Builder;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
@ -6,6 +7,7 @@ use diesel::prelude::*;
|
|||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use pulldown_cmark::{Options, Parser};
|
||||
use rand::seq::SliceRandom;
|
||||
use warp::Rejection;
|
||||
|
||||
#[derive(Queryable)]
|
||||
|
@ -29,6 +31,47 @@ impl Paste {
|
|||
}
|
||||
}
|
||||
|
||||
const CHARACTERS: &[u8] = b"23456789bcdfghjkmnpqrstvwxzBCDFGHJKLMNPQRSTVWX-";
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[table_name = "pastes"]
|
||||
struct InsertPaste {
|
||||
identifier: String,
|
||||
delete_at: Option<DateTime<Utc>>,
|
||||
language_id: i32,
|
||||
paste: String,
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
connection: &Connection,
|
||||
delete_at: Option<DateTime<Utc>>,
|
||||
language: &str,
|
||||
paste: String,
|
||||
) -> Result<String, Rejection> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let identifier: String = (0..10)
|
||||
.map(|_| char::from(*CHARACTERS.choose(&mut rng).expect("a random character")))
|
||||
.collect();
|
||||
let language_id = languages::table
|
||||
.select(languages::language_id)
|
||||
.filter(languages::identifier.eq(language))
|
||||
.get_result(connection)
|
||||
.optional()
|
||||
.map_err(warp::reject::custom)?
|
||||
.ok_or_else(|| warp::reject::custom(CustomRejection::UnrecognizedLanguageIdentifier))?;
|
||||
let insert_paste = InsertPaste {
|
||||
identifier,
|
||||
delete_at,
|
||||
language_id,
|
||||
paste,
|
||||
};
|
||||
diesel::insert_into(pastes::table)
|
||||
.values(&insert_paste)
|
||||
.execute(connection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
Ok(insert_paste.identifier)
|
||||
}
|
||||
|
||||
pub struct ExternPaste {
|
||||
pub paste: String,
|
||||
pub language_id: i32,
|
||||
|
|
26
src/models/rejection.rs
Normal file
26
src/models/rejection.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
use warp::http::StatusCode;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CustomRejection {
|
||||
UnrecognizedLanguageIdentifier,
|
||||
}
|
||||
|
||||
impl CustomRejection {
|
||||
pub fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
Self::UnrecognizedLanguageIdentifier => StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CustomRejection {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
match self {
|
||||
Self::UnrecognizedLanguageIdentifier => write!(f, "unrecognized language identifier"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CustomRejection {}
|
|
@ -1 +1,2 @@
|
|||
pub mod languages;
|
||||
pub mod pastes;
|
||||
|
|
32
src/routes/api_v1/pastes.rs
Normal file
32
src/routes/api_v1/pastes.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use crate::models::paste;
|
||||
use crate::Connection;
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::Future;
|
||||
use futures03::TryFutureExt;
|
||||
use serde::Deserialize;
|
||||
use tokio_executor::blocking;
|
||||
use warp::Rejection;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PasteForm {
|
||||
delete_at: Option<DateTime<Utc>>,
|
||||
#[serde(default = "default_language")]
|
||||
language: String,
|
||||
code: String,
|
||||
}
|
||||
|
||||
fn default_language() -> String {
|
||||
"plain-text".into()
|
||||
}
|
||||
|
||||
pub fn insert_paste(
|
||||
PasteForm {
|
||||
delete_at,
|
||||
language,
|
||||
code,
|
||||
}: PasteForm,
|
||||
connection: Connection,
|
||||
) -> impl Future<Item = String, Error = Rejection> {
|
||||
blocking::run(move || paste::insert(&connection, delete_at, &language, code)).compat()
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
use crate::schema::{languages, pastes};
|
||||
use crate::models::paste;
|
||||
use crate::Connection;
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use diesel::prelude::*;
|
||||
use chrono::{Duration, Utc};
|
||||
use futures::Future;
|
||||
use futures03::TryFutureExt;
|
||||
use rand::prelude::*;
|
||||
use serde::de::IgnoredAny;
|
||||
use serde::Deserialize;
|
||||
use tokio_executor::blocking;
|
||||
|
@ -12,8 +10,6 @@ use warp::http::header::LOCATION;
|
|||
use warp::http::StatusCode;
|
||||
use warp::{reply, Rejection, Reply};
|
||||
|
||||
const CHARACTERS: &[u8] = b"23456789bcdfghjkmnpqrstvwxzBCDFGHJKLMNPQRSTVWX-";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PasteForm {
|
||||
language: String,
|
||||
|
@ -21,49 +17,21 @@ pub struct PasteForm {
|
|||
autodelete: Option<IgnoredAny>,
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[table_name = "pastes"]
|
||||
struct NewPaste {
|
||||
identifier: String,
|
||||
delete_at: Option<DateTime<Utc>>,
|
||||
language_id: i32,
|
||||
paste: String,
|
||||
}
|
||||
|
||||
pub fn insert_paste(
|
||||
form: PasteForm,
|
||||
PasteForm {
|
||||
language,
|
||||
code,
|
||||
autodelete,
|
||||
}: PasteForm,
|
||||
connection: Connection,
|
||||
) -> impl Future<Item = impl Reply, Error = Rejection> {
|
||||
blocking::run(move || {
|
||||
let mut rng = thread_rng();
|
||||
let identifier: String = (0..10)
|
||||
.map(|_| char::from(*CHARACTERS.choose(&mut rng).expect("a random character")))
|
||||
.collect();
|
||||
let cloned_identifier = identifier.clone();
|
||||
let PasteForm {
|
||||
language,
|
||||
code,
|
||||
autodelete,
|
||||
} = form;
|
||||
let delete_at = autodelete.map(|_| Utc::now() + Duration::hours(24));
|
||||
let language_id = languages::table
|
||||
.select(languages::language_id)
|
||||
.filter(languages::identifier.eq(language))
|
||||
.get_result(&connection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
diesel::insert_into(pastes::table)
|
||||
.values(NewPaste {
|
||||
identifier,
|
||||
delete_at,
|
||||
language_id,
|
||||
paste: code,
|
||||
})
|
||||
.execute(&connection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
let identifier = paste::insert(&connection, delete_at, &language, code)?;
|
||||
Ok(reply::with_header(
|
||||
StatusCode::SEE_OTHER,
|
||||
LOCATION,
|
||||
format!("/{}", cloned_identifier),
|
||||
format!("/{}", identifier),
|
||||
))
|
||||
})
|
||||
.compat()
|
||||
|
|
|
@ -7,6 +7,7 @@ mod insert_paste;
|
|||
mod raw_paste;
|
||||
mod run;
|
||||
|
||||
use crate::models::rejection::CustomRejection;
|
||||
use crate::models::session::Session;
|
||||
use crate::templates::{self, RenderRucte};
|
||||
use crate::Connection;
|
||||
|
@ -110,14 +111,19 @@ fn api_v0(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
|||
language.or(run).boxed()
|
||||
}
|
||||
|
||||
fn api_v1_languages(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||
path!("api" / "v1")
|
||||
.and(warp::path("languages"))
|
||||
fn api_v1(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||
let languages = warp::path("languages")
|
||||
.and(warp::path::end())
|
||||
.and(warp::get2())
|
||||
.and(connection(pool.clone()))
|
||||
.and_then(api_v1::languages::languages);
|
||||
let pastes = warp::path("pastes")
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::content_length_limit(1_000_000))
|
||||
.and(warp::body::form())
|
||||
.and(connection(pool))
|
||||
.and_then(api_v1::languages::languages)
|
||||
.boxed()
|
||||
.and_then(api_v1::pastes::insert_paste);
|
||||
path!("api" / "v1").and(languages.or(pastes)).boxed()
|
||||
}
|
||||
|
||||
fn static_dir() -> BoxedFilter<(impl Reply,)> {
|
||||
|
@ -141,7 +147,7 @@ pub fn routes(
|
|||
.or(favicon())
|
||||
.or(options(pool.clone()))
|
||||
.or(api_v0(pool.clone()))
|
||||
.or(api_v1_languages(pool.clone()))
|
||||
.or(api_v1(pool.clone()))
|
||||
.or(raw_paste(pool.clone()))
|
||||
.or(display_paste(pool.clone()))
|
||||
.or(static_dir())
|
||||
|
@ -174,7 +180,12 @@ fn not_found(pool: PgPool) -> impl Clone + Fn(Rejection) -> NotFoundFuture {
|
|||
move |rejection| {
|
||||
let pool = pool.clone();
|
||||
async move {
|
||||
if rejection.is_not_found() {
|
||||
if let Some(rejection) = rejection.find_cause::<CustomRejection>() {
|
||||
Response::builder()
|
||||
.status(rejection.status_code())
|
||||
.body(rejection.to_string().into_bytes())
|
||||
.map_err(warp::reject::custom)
|
||||
} else if rejection.is_not_found() {
|
||||
let session = get_session(pool.clone()).await?;
|
||||
session
|
||||
.render()
|
||||
|
|
Loading…
Reference in New Issue
Block a user