Code splitting
This commit is contained in:
parent
7391b2db26
commit
573195d09c
6 changed files with 276 additions and 235 deletions
|
@ -71,8 +71,10 @@ pub fn static_resource(input: TokenStream) -> TokenStream {
|
||||||
vec![Options, Head, Get]
|
vec![Options, Head, Get]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn head(&self) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
fn head(&self) ->
|
||||||
futures::finished(Response::new()
|
::futures::BoxFuture<::hyper::server::Response, Box<::std::error::Error + Send + Sync>>
|
||||||
|
{
|
||||||
|
::futures::finished(::hyper::server::Response::new()
|
||||||
.with_status(::hyper::StatusCode::Ok)
|
.with_status(::hyper::StatusCode::Ok)
|
||||||
.with_header(::hyper::header::ContentType(
|
.with_header(::hyper::header::ContentType(
|
||||||
#mime.parse().expect("Statically supplied mime type must be parseable")))
|
#mime.parse().expect("Statically supplied mime type must be parseable")))
|
||||||
|
@ -85,7 +87,9 @@ pub fn static_resource(input: TokenStream) -> TokenStream {
|
||||||
).boxed()
|
).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(self: Box<Self>) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
fn get(self: Box<Self>) ->
|
||||||
|
::futures::BoxFuture<::hyper::server::Response, Box<::std::error::Error + Send + Sync>>
|
||||||
|
{
|
||||||
let body = include_bytes!(#abs_filename);
|
let body = include_bytes!(#abs_filename);
|
||||||
|
|
||||||
self.head().map(move |head|
|
self.head().map(move |head|
|
||||||
|
@ -95,17 +99,19 @@ pub fn static_resource(input: TokenStream) -> TokenStream {
|
||||||
).boxed()
|
).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn put(self: Box<Self>, _body: hyper::Body) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
fn put(self: Box<Self>, _body: ::hyper::Body) ->
|
||||||
futures::finished(self.method_not_allowed()).boxed()
|
::futures::BoxFuture<::hyper::server::Response, Box<::std::error::Error + Send + Sync>>
|
||||||
|
{
|
||||||
|
::futures::finished(self.method_not_allowed()).boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #impl_generics #name #ty_generics #where_clause {
|
impl #impl_generics #name #ty_generics #where_clause {
|
||||||
fn checksum() -> &'static str {
|
pub fn checksum() -> &'static str {
|
||||||
#checksum
|
#checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
fn etag() -> ::hyper::header::EntityTag {
|
pub fn etag() -> ::hyper::header::EntityTag {
|
||||||
::hyper::header::EntityTag::new(false, Self::checksum().to_owned())
|
::hyper::header::EntityTag::new(false, Self::checksum().to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
158
src/article_resource.rs
Normal file
158
src/article_resource.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
use futures::{self, Future};
|
||||||
|
use hyper;
|
||||||
|
use hyper::header::ContentType;
|
||||||
|
use hyper::mime;
|
||||||
|
use hyper::server::*;
|
||||||
|
use serde_json;
|
||||||
|
use serde_urlencoded;
|
||||||
|
|
||||||
|
use assets::{StyleCss, ScriptJs};
|
||||||
|
use models;
|
||||||
|
use site::Layout;
|
||||||
|
use state::State;
|
||||||
|
use web::Resource;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref TEXT_HTML: mime::Mime = "text/html;charset=utf-8".parse().unwrap();
|
||||||
|
static ref APPLICATION_JSON: mime::Mime = "application/json".parse().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_markdown(src: &str) -> String {
|
||||||
|
use pulldown_cmark::Event;
|
||||||
|
|
||||||
|
struct EscapeHtml<'a, I: Iterator<Item=Event<'a>>> {
|
||||||
|
inner: I,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: Iterator<Item=Event<'a>>> EscapeHtml<'a, I> {
|
||||||
|
fn new(inner: I) -> EscapeHtml<'a, I> {
|
||||||
|
EscapeHtml { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: Iterator<Item=Event<'a>>> Iterator for EscapeHtml<'a, I> {
|
||||||
|
type Item = Event<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
use pulldown_cmark::Event::{Text, Html, InlineHtml};
|
||||||
|
|
||||||
|
match self.inner.next() {
|
||||||
|
Some(Html(x)) => Some(Text(x)),
|
||||||
|
Some(InlineHtml(x)) => Some(Text(x)),
|
||||||
|
x => x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use pulldown_cmark::{Parser, html};
|
||||||
|
|
||||||
|
let p = EscapeHtml::new(Parser::new(src));
|
||||||
|
let mut buf = String::new();
|
||||||
|
html::push_html(&mut buf, p);
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ArticleResource {
|
||||||
|
state: State,
|
||||||
|
data: models::ArticleRevision,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArticleResource {
|
||||||
|
pub fn new(state: State, data: models::ArticleRevision) -> Self {
|
||||||
|
Self { state, data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resource for ArticleResource {
|
||||||
|
fn allow(&self) -> Vec<hyper::Method> {
|
||||||
|
use hyper::Method::*;
|
||||||
|
vec![Options, Head, Get, Put]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn head(&self) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
||||||
|
futures::finished(Response::new()
|
||||||
|
.with_status(hyper::StatusCode::Ok)
|
||||||
|
.with_header(ContentType(TEXT_HTML.clone()))
|
||||||
|
).boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(self: Box<Self>) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
||||||
|
use chrono::{self, TimeZone, Local};
|
||||||
|
|
||||||
|
#[derive(BartDisplay)]
|
||||||
|
#[template="templates/article_revision.html"]
|
||||||
|
struct Template<'a> {
|
||||||
|
article_id: i32,
|
||||||
|
revision: i32,
|
||||||
|
created: &'a chrono::DateTime<Local>,
|
||||||
|
|
||||||
|
title: &'a str,
|
||||||
|
raw: &'a str,
|
||||||
|
rendered: String,
|
||||||
|
|
||||||
|
script_js_checksum: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.head().map(move |head|
|
||||||
|
head
|
||||||
|
.with_body(Layout {
|
||||||
|
title: &self.data.title,
|
||||||
|
body: &Template {
|
||||||
|
article_id: self.data.article_id,
|
||||||
|
revision: self.data.revision,
|
||||||
|
created: &Local.from_utc_datetime(&self.data.created),
|
||||||
|
title: &self.data.title,
|
||||||
|
raw: &self.data.body,
|
||||||
|
rendered: render_markdown(&self.data.body),
|
||||||
|
script_js_checksum: ScriptJs::checksum(),
|
||||||
|
},
|
||||||
|
style_css_checksum: StyleCss::checksum(),
|
||||||
|
}.to_string())
|
||||||
|
).boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put(self: Box<Self>, body: hyper::Body) ->
|
||||||
|
futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>>
|
||||||
|
{
|
||||||
|
// TODO Check incoming Content-Type
|
||||||
|
|
||||||
|
use chrono::{TimeZone, Local};
|
||||||
|
use futures::Stream;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UpdateArticle {
|
||||||
|
base_revision: i32,
|
||||||
|
body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct PutResponse<'a> {
|
||||||
|
revision: i32,
|
||||||
|
rendered: &'a str,
|
||||||
|
created: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.data.article_id, update.base_revision, update.body)
|
||||||
|
})
|
||||||
|
.and_then(|updated| {
|
||||||
|
futures::finished(Response::new()
|
||||||
|
.with_status(hyper::StatusCode::Ok)
|
||||||
|
.with_header(ContentType(APPLICATION_JSON.clone()))
|
||||||
|
.with_body(serde_json::to_string(&PutResponse {
|
||||||
|
revision: updated.revision,
|
||||||
|
rendered: &render_markdown(&updated.body),
|
||||||
|
created: &Local.from_utc_datetime(&updated.created).to_string(),
|
||||||
|
}).expect("Should never fail"))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
19
src/assets.rs
Normal file
19
src/assets.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use futures::Future;
|
||||||
|
use web::Resource;
|
||||||
|
|
||||||
|
#[derive(StaticResource)]
|
||||||
|
#[filename = "assets/style.css"]
|
||||||
|
#[mime = "text/css"]
|
||||||
|
pub struct StyleCss;
|
||||||
|
|
||||||
|
#[derive(StaticResource)]
|
||||||
|
#[filename = "assets/script.js"]
|
||||||
|
#[mime = "application/javascript"]
|
||||||
|
pub struct ScriptJs;
|
||||||
|
|
||||||
|
// SIL Open Font License 1.1: http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
|
||||||
|
// Copyright 2015 The Amatic SC Project Authors (contact@sansoxygen.com)
|
||||||
|
#[derive(StaticResource)]
|
||||||
|
#[filename = "assets/amatic-sc-v9-latin-regular.woff"]
|
||||||
|
#[mime = "application/font-woff"]
|
||||||
|
pub struct AmaticFont;
|
|
@ -18,12 +18,15 @@ extern crate serde_urlencoded;
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
mod article_resource;
|
||||||
|
mod assets;
|
||||||
mod db;
|
mod db;
|
||||||
mod models;
|
mod models;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod site;
|
mod site;
|
||||||
mod state;
|
mod state;
|
||||||
mod web;
|
mod web;
|
||||||
|
mod wiki_lookup;
|
||||||
|
|
||||||
fn args<'a>() -> clap::ArgMatches<'a> {
|
fn args<'a>() -> clap::ArgMatches<'a> {
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
|
@ -58,12 +61,13 @@ fn core_main() -> Result<(), Box<std::error::Error>> {
|
||||||
let cpu_pool = futures_cpupool::CpuPool::new_num_cpus();
|
let cpu_pool = futures_cpupool::CpuPool::new_num_cpus();
|
||||||
|
|
||||||
let state = state::State::new(db_pool, cpu_pool);
|
let state = state::State::new(db_pool, cpu_pool);
|
||||||
|
let lookup = wiki_lookup::WikiLookup::new(state);
|
||||||
|
|
||||||
let server =
|
let server =
|
||||||
hyper::server::Http::new()
|
hyper::server::Http::new()
|
||||||
.bind(
|
.bind(
|
||||||
&SocketAddr::new(bind_host, bind_port),
|
&SocketAddr::new(bind_host, bind_port),
|
||||||
move || Ok(site::Site::new(state.clone()))
|
move || Ok(site::Site::new(lookup.clone()))
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
println!("Listening on http://{}", server.local_addr().unwrap());
|
println!("Listening on http://{}", server.local_addr().unwrap());
|
||||||
|
|
233
src/site.rs
233
src/site.rs
|
@ -1,7 +1,6 @@
|
||||||
// #[derive(BartDisplay)] can cause unused extern crates warning:
|
// #[derive(BartDisplay)] can cause unused extern crates warning:
|
||||||
#![allow(unused_extern_crates)]
|
#![allow(unused_extern_crates)]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use futures::{self, Future};
|
use futures::{self, Future};
|
||||||
|
@ -9,56 +8,18 @@ use hyper;
|
||||||
use hyper::header::ContentType;
|
use hyper::header::ContentType;
|
||||||
use hyper::mime;
|
use hyper::mime;
|
||||||
use hyper::server::*;
|
use hyper::server::*;
|
||||||
use serde_json;
|
|
||||||
use serde_urlencoded;
|
|
||||||
|
|
||||||
use models;
|
use assets::StyleCss;
|
||||||
use state::State;
|
use web::Lookup;
|
||||||
use web::{Lookup, Resource};
|
use wiki_lookup::WikiLookup;
|
||||||
|
|
||||||
use pulldown_cmark::Event;
|
|
||||||
|
|
||||||
struct EscapeHtml<'a, I: Iterator<Item=Event<'a>>> {
|
|
||||||
inner: I,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, I: Iterator<Item=Event<'a>>> EscapeHtml<'a, I> {
|
|
||||||
fn new(inner: I) -> EscapeHtml<'a, I> {
|
|
||||||
EscapeHtml { inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, I: Iterator<Item=Event<'a>>> Iterator for EscapeHtml<'a, I> {
|
|
||||||
type Item = Event<'a>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
use pulldown_cmark::Event::{Text, Html, InlineHtml};
|
|
||||||
|
|
||||||
match self.inner.next() {
|
|
||||||
Some(Html(x)) => Some(Text(x)),
|
|
||||||
Some(InlineHtml(x)) => Some(Text(x)),
|
|
||||||
x => x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_markdown(src: &str) -> String {
|
|
||||||
use pulldown_cmark::{Parser, html};
|
|
||||||
|
|
||||||
let p = EscapeHtml::new(Parser::new(src));
|
|
||||||
let mut buf = String::new();
|
|
||||||
html::push_html(&mut buf, p);
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref TEXT_HTML: mime::Mime = "text/html;charset=utf-8".parse().unwrap();
|
static ref TEXT_HTML: mime::Mime = "text/html;charset=utf-8".parse().unwrap();
|
||||||
static ref APPLICATION_JSON: mime::Mime = "application/json".parse().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BartDisplay)]
|
#[derive(BartDisplay)]
|
||||||
#[template = "templates/layout.html"]
|
#[template = "templates/layout.html"]
|
||||||
struct Layout<'a, T: 'a + fmt::Display> {
|
pub struct Layout<'a, T: 'a + fmt::Display> {
|
||||||
pub title: &'a str,
|
pub title: &'a str,
|
||||||
pub body: &'a T,
|
pub body: &'a T,
|
||||||
pub style_css_checksum: &'a str,
|
pub style_css_checksum: &'a str,
|
||||||
|
@ -72,195 +33,13 @@ struct NotFound;
|
||||||
#[template = "templates/500.html"]
|
#[template = "templates/500.html"]
|
||||||
struct InternalServerError;
|
struct InternalServerError;
|
||||||
|
|
||||||
#[derive(StaticResource)]
|
|
||||||
#[filename = "assets/style.css"]
|
|
||||||
#[mime = "text/css"]
|
|
||||||
struct StyleCss;
|
|
||||||
|
|
||||||
#[derive(StaticResource)]
|
|
||||||
#[filename = "assets/script.js"]
|
|
||||||
#[mime = "application/javascript"]
|
|
||||||
struct ScriptJs;
|
|
||||||
|
|
||||||
// SIL Open Font License 1.1: http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
|
|
||||||
// Copyright 2015 The Amatic SC Project Authors (contact@sansoxygen.com)
|
|
||||||
#[derive(StaticResource)]
|
|
||||||
#[filename = "assets/amatic-sc-v9-latin-regular.woff"]
|
|
||||||
#[mime = "application/font-woff"]
|
|
||||||
struct AmaticFont;
|
|
||||||
|
|
||||||
struct WikiLookup {
|
|
||||||
state: State,
|
|
||||||
lookup_map: HashMap<String, Box<Fn() -> Box<Resource + Sync + Send>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WikiLookup {
|
|
||||||
fn new(state: State) -> WikiLookup {
|
|
||||||
let mut lookup_map = HashMap::new();
|
|
||||||
|
|
||||||
lookup_map.insert(
|
|
||||||
format!("/_assets/style-{}.css", StyleCss::checksum()),
|
|
||||||
Box::new(|| Box::new(StyleCss) as Box<Resource + Sync + Send>)
|
|
||||||
as Box<Fn() -> Box<Resource + Sync + Send>>
|
|
||||||
);
|
|
||||||
|
|
||||||
lookup_map.insert(
|
|
||||||
format!("/_assets/script-{}.js", ScriptJs::checksum()),
|
|
||||||
Box::new(|| Box::new(ScriptJs) as Box<Resource + Sync + Send>)
|
|
||||||
as Box<Fn() -> Box<Resource + Sync + Send>>
|
|
||||||
);
|
|
||||||
|
|
||||||
lookup_map.insert(
|
|
||||||
format!("/_assets/amatic-sc-v9-latin-regular.woff"),
|
|
||||||
Box::new(|| Box::new(AmaticFont) as Box<Resource + Sync + Send>)
|
|
||||||
as Box<Fn() -> Box<Resource + Sync + Send>>
|
|
||||||
);
|
|
||||||
|
|
||||||
WikiLookup { state, lookup_map }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Lookup for WikiLookup {
|
|
||||||
type Resource = Box<Resource + Send + Sync>;
|
|
||||||
type Error = Box<::std::error::Error + Send + Sync>;
|
|
||||||
type Future = futures::BoxFuture<Option<Self::Resource>, Self::Error>;
|
|
||||||
|
|
||||||
fn lookup(&self, path: &str, _query: Option<&str>, _fragment: Option<&str>) -> Self::Future {
|
|
||||||
assert!(path.starts_with("/"));
|
|
||||||
|
|
||||||
if path.starts_with("/_") {
|
|
||||||
// Reserved namespace
|
|
||||||
|
|
||||||
return futures::finished(
|
|
||||||
self.lookup_map.get(path).map(|x| x())
|
|
||||||
).boxed();
|
|
||||||
}
|
|
||||||
|
|
||||||
let slug = &path[1..];
|
|
||||||
if let Ok(article_id) = slug.parse() {
|
|
||||||
let state = self.state.clone();
|
|
||||||
self.state.get_article_revision_by_id(article_id)
|
|
||||||
.and_then(|x| Ok(x.map(move |article| Box::new(ArticleResource::new(state, article)) as Box<Resource + Sync + Send>)))
|
|
||||||
.boxed()
|
|
||||||
} else {
|
|
||||||
futures::finished(None).boxed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ArticleResource {
|
|
||||||
state: State,
|
|
||||||
data: models::ArticleRevision,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArticleResource {
|
|
||||||
fn new(state: State, data: models::ArticleRevision) -> Self {
|
|
||||||
Self { state, data }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resource for ArticleResource {
|
|
||||||
fn allow(&self) -> Vec<hyper::Method> {
|
|
||||||
use hyper::Method::*;
|
|
||||||
vec![Options, Head, Get, Put]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn head(&self) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
|
||||||
futures::finished(Response::new()
|
|
||||||
.with_status(hyper::StatusCode::Ok)
|
|
||||||
.with_header(ContentType(TEXT_HTML.clone()))
|
|
||||||
).boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(self: Box<Self>) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
|
||||||
use chrono::{self, TimeZone, Local};
|
|
||||||
|
|
||||||
#[derive(BartDisplay)]
|
|
||||||
#[template="templates/article_revision.html"]
|
|
||||||
struct Template<'a> {
|
|
||||||
article_id: i32,
|
|
||||||
revision: i32,
|
|
||||||
created: &'a chrono::DateTime<Local>,
|
|
||||||
|
|
||||||
title: &'a str,
|
|
||||||
raw: &'a str,
|
|
||||||
rendered: String,
|
|
||||||
|
|
||||||
script_js_checksum: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.head().map(move |head|
|
|
||||||
head
|
|
||||||
.with_body(Layout {
|
|
||||||
title: &self.data.title,
|
|
||||||
body: &Template {
|
|
||||||
article_id: self.data.article_id,
|
|
||||||
revision: self.data.revision,
|
|
||||||
created: &Local.from_utc_datetime(&self.data.created),
|
|
||||||
title: &self.data.title,
|
|
||||||
raw: &self.data.body,
|
|
||||||
rendered: render_markdown(&self.data.body),
|
|
||||||
script_js_checksum: ScriptJs::checksum(),
|
|
||||||
},
|
|
||||||
style_css_checksum: StyleCss::checksum(),
|
|
||||||
}.to_string())
|
|
||||||
).boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn put(self: Box<Self>, body: hyper::Body) -> futures::BoxFuture<Response, Box<::std::error::Error + Send + Sync>> {
|
|
||||||
// TODO Check incoming Content-Type
|
|
||||||
|
|
||||||
use chrono::{TimeZone, Local};
|
|
||||||
use futures::Stream;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct UpdateArticle {
|
|
||||||
base_revision: i32,
|
|
||||||
body: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct PutResponse<'a> {
|
|
||||||
revision: i32,
|
|
||||||
rendered: &'a str,
|
|
||||||
created: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
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.data.article_id, update.base_revision, update.body)
|
|
||||||
})
|
|
||||||
.and_then(|updated| {
|
|
||||||
futures::finished(Response::new()
|
|
||||||
.with_status(hyper::StatusCode::Ok)
|
|
||||||
.with_header(ContentType(APPLICATION_JSON.clone()))
|
|
||||||
.with_body(serde_json::to_string(&PutResponse {
|
|
||||||
revision: updated.revision,
|
|
||||||
rendered: &render_markdown(&updated.body),
|
|
||||||
created: &Local.from_utc_datetime(&updated.created).to_string(),
|
|
||||||
}).expect("Should never fail"))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct Site {
|
pub struct Site {
|
||||||
root: WikiLookup,
|
root: WikiLookup,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Site {
|
impl Site {
|
||||||
pub fn new(state: State) -> Site {
|
pub fn new(root: WikiLookup) -> Site {
|
||||||
Site {
|
Site { root }
|
||||||
root: WikiLookup::new(state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_found() -> Response {
|
fn not_found() -> Response {
|
||||||
|
|
75
src/wiki_lookup.rs
Normal file
75
src/wiki_lookup.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use futures::{self, Future};
|
||||||
|
|
||||||
|
use assets::*;
|
||||||
|
use article_resource::ArticleResource;
|
||||||
|
use state::State;
|
||||||
|
use web::{Lookup, Resource};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref LOOKUP_MAP: HashMap<String, Box<Fn() -> Box<Resource + Sync + Send> + Sync + Send>> = {
|
||||||
|
let mut lookup_map = HashMap::new();
|
||||||
|
|
||||||
|
lookup_map.insert(
|
||||||
|
format!("/_assets/style-{}.css", StyleCss::checksum()),
|
||||||
|
Box::new(|| Box::new(StyleCss) as Box<Resource + Sync + Send>)
|
||||||
|
as Box<Fn() -> Box<Resource + Sync + Send> + Sync + Send>
|
||||||
|
);
|
||||||
|
|
||||||
|
lookup_map.insert(
|
||||||
|
format!("/_assets/script-{}.js", ScriptJs::checksum()),
|
||||||
|
Box::new(|| Box::new(ScriptJs) as Box<Resource + Sync + Send>)
|
||||||
|
as Box<Fn() -> Box<Resource + Sync + Send> + Sync + Send>
|
||||||
|
);
|
||||||
|
|
||||||
|
lookup_map.insert(
|
||||||
|
format!("/_assets/amatic-sc-v9-latin-regular.woff"),
|
||||||
|
Box::new(|| Box::new(AmaticFont) as Box<Resource + Sync + Send>)
|
||||||
|
as Box<Fn() -> Box<Resource + Sync + Send> + Sync + Send>
|
||||||
|
);
|
||||||
|
|
||||||
|
lookup_map
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct WikiLookup {
|
||||||
|
state: State
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WikiLookup {
|
||||||
|
pub fn new(state: State) -> WikiLookup {
|
||||||
|
WikiLookup { state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lookup for WikiLookup {
|
||||||
|
type Resource = Box<Resource + Send + Sync>;
|
||||||
|
type Error = Box<::std::error::Error + Send + Sync>;
|
||||||
|
type Future = futures::BoxFuture<Option<Self::Resource>, Self::Error>;
|
||||||
|
|
||||||
|
fn lookup(&self, path: &str, _query: Option<&str>, _fragment: Option<&str>) -> Self::Future {
|
||||||
|
assert!(path.starts_with("/"));
|
||||||
|
|
||||||
|
if path.starts_with("/_") {
|
||||||
|
// Reserved namespace
|
||||||
|
|
||||||
|
return futures::finished(
|
||||||
|
LOOKUP_MAP.get(path).map(|x| x())
|
||||||
|
).boxed();
|
||||||
|
}
|
||||||
|
|
||||||
|
let slug = &path[1..];
|
||||||
|
if let Ok(article_id) = slug.parse() {
|
||||||
|
let state = self.state.clone();
|
||||||
|
self.state.get_article_revision_by_id(article_id)
|
||||||
|
.and_then(|x| Ok(x.map(move |article|
|
||||||
|
Box::new(ArticleResource::new(state, article)) as Box<Resource + Sync + Send>
|
||||||
|
)))
|
||||||
|
.boxed()
|
||||||
|
} else {
|
||||||
|
futures::finished(None).boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue