Support editing articles without JavaScript
This commit is contained in:
parent
db2d8f5d76
commit
5b37a41632
6 changed files with 97 additions and 28 deletions
|
@ -2,5 +2,6 @@ use hyper::mime;
|
|||
|
||||
lazy_static! {
|
||||
pub static ref TEXT_HTML: mime::Mime = "text/html;charset=utf-8".parse().unwrap();
|
||||
pub static ref TEXT_PLAIN: mime::Mime = "text/plain;charset=utf-8".parse().unwrap();
|
||||
pub static ref APPLICATION_JSON: mime::Mime = "application/json".parse().unwrap();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,16 @@ pub struct ArticleRevision {
|
|||
pub author: Option<String>,
|
||||
}
|
||||
|
||||
impl ArticleRevision {
|
||||
pub fn link(&self) -> &str {
|
||||
if self.slug.is_empty() {
|
||||
"."
|
||||
} else {
|
||||
&self.slug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable)]
|
||||
pub struct ArticleRevisionStub {
|
||||
pub sequence_number: i32,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use chrono::{TimeZone, DateTime, Local};
|
||||
use futures::{self, Future};
|
||||
use hyper;
|
||||
use hyper::header::ContentType;
|
||||
use hyper::header::{ContentType, Location};
|
||||
use hyper::server::*;
|
||||
use serde_json;
|
||||
use serde_urlencoded;
|
||||
|
@ -22,6 +22,13 @@ pub struct ArticleResource {
|
|||
edit: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateArticle {
|
||||
base_revision: i32,
|
||||
title: String,
|
||||
body: String,
|
||||
}
|
||||
|
||||
impl ArticleResource {
|
||||
pub fn new(state: State, article_id: i32, revision: i32, edit: bool) -> Self {
|
||||
Self { state, article_id, revision, edit }
|
||||
|
@ -55,7 +62,7 @@ pub fn last_updated(article_id: i32, created: &DateTime<Local>, author: Option<&
|
|||
impl Resource for ArticleResource {
|
||||
fn allow(&self) -> Vec<hyper::Method> {
|
||||
use hyper::Method::*;
|
||||
vec![Options, Head, Get, Put]
|
||||
vec![Options, Head, Get, Put, Post]
|
||||
}
|
||||
|
||||
fn head(&self) -> ResponseFuture {
|
||||
|
@ -114,13 +121,6 @@ impl Resource for ArticleResource {
|
|||
|
||||
use futures::Stream;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateArticle {
|
||||
base_revision: i32,
|
||||
title: String,
|
||||
body: String,
|
||||
}
|
||||
|
||||
#[derive(BartDisplay)]
|
||||
#[template="templates/article_contents.html"]
|
||||
struct Template<'a> {
|
||||
|
@ -169,4 +169,30 @@ impl Resource for ArticleResource {
|
|||
})
|
||||
)
|
||||
}
|
||||
|
||||
fn post(self: Box<Self>, body: hyper::Body, identity: Option<String>) -> ResponseFuture {
|
||||
// TODO Check incoming Content-Type
|
||||
|
||||
use futures::Stream;
|
||||
|
||||
Box::new(body
|
||||
.concat2()
|
||||
.map_err(Into::into)
|
||||
.and_then(|body| {
|
||||
serde_urlencoded::from_bytes(&body)
|
||||
.map_err(Into::into)
|
||||
})
|
||||
.and_then(move |update: UpdateArticle| {
|
||||
self.state.update_article(self.article_id, update.base_revision, update.title, update.body, identity)
|
||||
})
|
||||
.and_then(|updated| {
|
||||
futures::finished(Response::new()
|
||||
.with_status(hyper::StatusCode::SeeOther)
|
||||
.with_header(ContentType(TEXT_PLAIN.clone()))
|
||||
.with_header(Location::new(updated.link().to_owned()))
|
||||
.with_body("See other")
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use futures::{self, Future};
|
||||
use hyper;
|
||||
use hyper::header::ContentType;
|
||||
use hyper::header::{ContentType, Location};
|
||||
use hyper::server::*;
|
||||
use serde_json;
|
||||
use serde_urlencoded;
|
||||
|
@ -29,6 +29,13 @@ pub struct NewArticleResource {
|
|||
slug: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateArticle {
|
||||
base_revision: String,
|
||||
title: String,
|
||||
body: String,
|
||||
}
|
||||
|
||||
impl NewArticleResource {
|
||||
pub fn new(state: State, slug: Option<String>) -> Self {
|
||||
Self { state, slug }
|
||||
|
@ -98,13 +105,6 @@ impl Resource for NewArticleResource {
|
|||
use chrono::{TimeZone, Local};
|
||||
use futures::Stream;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateArticle {
|
||||
base_revision: String,
|
||||
title: String,
|
||||
body: String,
|
||||
}
|
||||
|
||||
#[derive(BartDisplay)]
|
||||
#[template="templates/article_contents.html"]
|
||||
struct Template<'a> {
|
||||
|
@ -158,4 +158,34 @@ impl Resource for NewArticleResource {
|
|||
})
|
||||
)
|
||||
}
|
||||
|
||||
fn post(self: Box<Self>, body: hyper::Body, identity: Option<String>) -> ResponseFuture {
|
||||
// TODO Check incoming Content-Type
|
||||
// TODO Refactor? Reduce duplication with ArticleResource::put?
|
||||
|
||||
use futures::Stream;
|
||||
|
||||
Box::new(body
|
||||
.concat2()
|
||||
.map_err(Into::into)
|
||||
.and_then(|body| {
|
||||
serde_urlencoded::from_bytes(&body)
|
||||
.map_err(Into::into)
|
||||
})
|
||||
.and_then(move |arg: CreateArticle| {
|
||||
if arg.base_revision != NEW {
|
||||
unimplemented!("Version update conflict");
|
||||
}
|
||||
self.state.create_article(self.slug.clone(), arg.title, arg.body, identity)
|
||||
})
|
||||
.and_then(|updated| {
|
||||
futures::finished(Response::new()
|
||||
.with_status(hyper::StatusCode::SeeOther)
|
||||
.with_header(ContentType(TEXT_PLAIN.clone()))
|
||||
.with_header(Location::new(updated.link().to_owned()))
|
||||
.with_body("See other")
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,6 +121,7 @@ impl Service for Site {
|
|||
Head => resource.head(),
|
||||
Get => resource.get(),
|
||||
Put => resource.put(body, identity),
|
||||
Post => resource.post(body, identity),
|
||||
_ => Box::new(futures::finished(resource.method_not_allowed()))
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use futures;
|
||||
use futures::{Future, Stream};
|
||||
use hyper::{self, header, mime, server};
|
||||
use hyper::server::Response;
|
||||
use std;
|
||||
|
@ -24,18 +25,18 @@ pub trait Resource {
|
|||
fn put(self: Box<Self>, body: hyper::Body, _identity: Option<String>) -> ResponseFuture
|
||||
where Self: 'static
|
||||
{
|
||||
use futures::{Future, Stream};
|
||||
|
||||
// TODO Cleanup by moving to the built in never type, !, when it stabilizes
|
||||
enum Never {};
|
||||
impl std::convert::From<Never> for hyper::Error {
|
||||
fn from(_: Never) -> hyper::Error {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(body
|
||||
.fold((), |_, _| -> Result<(), Never> { Ok(()) })
|
||||
.fold((), |_, _| -> Result<(), hyper::Error> { Ok(()) })
|
||||
.map_err(Into::into)
|
||||
.and_then(move |_| futures::finished(self.method_not_allowed()))
|
||||
)
|
||||
}
|
||||
|
||||
fn post(self: Box<Self>, body: hyper::Body, _identity: Option<String>) -> ResponseFuture
|
||||
where Self: 'static
|
||||
{
|
||||
Box::new(body
|
||||
.fold((), |_, _| -> Result<(), hyper::Error> { Ok(()) })
|
||||
.map_err(Into::into)
|
||||
.and_then(move |_| futures::finished(self.method_not_allowed()))
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue