/*
* This file is part of Event Web
*
* Event Web 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.
*
* Event Web 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 Event Web. If not, see .
*/
#![feature(proc_macro)]
#![feature(proc_macro_non_items)]
extern crate actix;
extern crate actix_web;
extern crate bcrypt;
extern crate chrono;
extern crate chrono_tz;
extern crate failure;
extern crate futures;
extern crate http;
extern crate maud;
extern crate serde;
#[macro_use]
extern crate serde_derive;
use actix::dev::{MessageResponse, ResponseChannel};
use actix::{Actor, Addr, Context, Handler, Message, Syn};
use actix_web::http::Method;
use actix_web::server::HttpServer;
use actix_web::*;
use chrono::offset::Utc;
use chrono::Datelike;
use chrono_tz::Tz;
use failure::{Fail, ResultExt};
use futures::future::Either;
use futures::{Future, IntoFuture};
use http::header;
mod error;
mod event;
mod views;
pub use error::{FrontendError, FrontendErrorKind, MissingField};
pub use event::{CreateEvent, Event, OptionEvent};
use views::{form, success};
pub type SendFuture = Box + Send>;
pub struct SendFutResponse
where
M: Message,
M::Result: Future + Send,
{
inner: M::Result,
}
impl SendFutResponse
where
M: Message,
M::Result: Future + Send,
{
pub fn new(inner: M::Result) -> Self {
SendFutResponse { inner }
}
}
impl MessageResponse for SendFutResponse
where
A: Actor,
M: Message,
M::Result: Future + Send,
{
fn handle(self, _: &mut A::Context, tx: Option)
where
R: ResponseChannel,
{
if let Some(tx) = tx {
tx.send(self.inner);
}
}
}
#[derive(Clone)]
pub struct EventHandler
where
T: Actor>
+ Handler
+ Handler
+ Handler
+ Clone,
{
handler: Addr,
}
impl EventHandler
where
T: Actor>
+ Handler
+ Handler
+ Handler
+ Clone,
{
pub fn new(handler: Addr) -> Self {
EventHandler { handler }
}
pub fn notify(
&self,
event: Event,
id: String,
) -> impl Future- {
self.handler
.send(NewEvent(event, id))
.then(|msg_res| match msg_res {
Ok(res) => Either::A(res),
Err(e) => Either::B(
Err(FrontendError::from(e.context(FrontendErrorKind::Canceled))).into_future(),
),
})
}
fn request_event(&self, id: String) -> impl Future
- {
self.handler
.send(LookupEvent(id))
.then(|msg_res| match msg_res {
Ok(res) => Either::A(res),
Err(e) => Either::B(
Err(FrontendError::from(e.context(FrontendErrorKind::Canceled))).into_future(),
),
})
}
fn edit_event(
&self,
event: Event,
id: String,
) -> impl Future
- {
self.handler
.send(EditEvent(event.clone(), id))
.then(|msg_res| match msg_res {
Ok(res) => Either::A(res),
Err(e) => Either::B(
Err(FrontendError::from(e.context(FrontendErrorKind::Canceled))).into_future(),
),
})
}
}
pub struct NewEvent(pub Event, pub String);
impl Message for NewEvent {
type Result = SendFuture<(), FrontendError>;
}
pub struct EditEvent(pub Event, pub String);
impl Message for EditEvent {
type Result = SendFuture<(), FrontendError>;
}
pub struct LookupEvent(pub String);
impl Message for LookupEvent {
type Result = SendFuture;
}
pub fn generate_secret(id: &str) -> Result {
bcrypt::hash(id, bcrypt::DEFAULT_COST)
.context(FrontendErrorKind::Generation)
.map_err(FrontendError::from)
}
pub fn verify_secret(id: &str, secret: &str) -> Result {
bcrypt::verify(id, secret)
.context(FrontendErrorKind::Verification)
.map_err(FrontendError::from)
}
fn load_form(
form_event: Option,
form_id: String,
form_url: String,
form_title: &str,
option_event: Option,
) -> HttpResponse {
let date = Utc::now().with_timezone(&Tz::US__Central);
let years = (date.year()..date.year() + 4).collect::>();
let months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
].into_iter()
.enumerate()
.map(|(u, m)| (u as u32, m))
.collect::>();
let days = (1..32).collect::>();
let hours = (0..24).collect::>();
let minutes = (0..60).collect::>();
let mut create_event = if let Some(ce) = form_event {
ce
} else {
CreateEvent::default_from(date)
};
if let Some(ref o) = option_event {
create_event.merge(o);
}
let timezones = [
Tz::US__Eastern,
Tz::US__Central,
Tz::US__Mountain,
Tz::US__Pacific,
].into_iter()
.map(|tz| tz.name())
.collect::>();
HttpResponse::Ok()
.header(header::CONTENT_TYPE, "text/html")
.body(
form(
create_event,
option_event,
form_url,
years,
months,
days,
hours,
minutes,
timezones,
form_id,
form_title,
).into_string(),
)
}
fn new_form(secret: Path) -> HttpResponse {
let id = secret.into_inner();
let submit_url = format!("/events/new/{}", id);
load_form(None, id, submit_url, "Event Bot | New Event", None)
}
fn edit_form(
path: Path,
state: State>,
) -> Box>
where
T: Actor>
+ Handler
+ Handler
+ Handler
+ Clone,
{
let id = path.into_inner();
let submit_url = format!("/events/edit/{}", id);
Box::new(state.request_event(id.clone()).map(move |event| {
load_form(
Some(event.into()),
id,
submit_url,
"Event Bot | Edit Event",
None,
)
}))
}
fn updated(
path: Path,
form: Form,
state: State>,
) -> Box>
where
T: Actor>
+ Handler
+ Handler
+ Handler
+ Clone,
{
let id = path.into_inner();
let id2 = id.clone();
let option_event = form.into_inner();
Box::new(
Event::from_option(option_event.clone())
.into_future()
.and_then(move |event| {
state.edit_event(event.clone(), id).map(|_| {
HttpResponse::Created()
.header(header::CONTENT_TYPE, "text/html")
.body(success(event, "Event Bot | Updated Event").into_string())
})
})
.or_else(move |_| {
let submit_url = format!("/events/edit/{}", id2);
Ok(load_form(
None,
id2,
submit_url,
"Event Bot | Edit Event",
Some(option_event),
))
}),
)
}
fn submitted(
path: Path,
form: Form,
state: State>,
) -> Box>
where
T: Actor>
+ Handler
+ Handler
+ Handler
+ Clone,
{
let id = path.into_inner();
let id2 = id.clone();
let option_event = form.into_inner();
Box::new(
Event::from_option(option_event.clone())
.into_future()
.map(move |event| {
state.handler.do_send(NewEvent(event.clone(), id));
HttpResponse::Created()
.header(header::CONTENT_TYPE, "text/html")
.body(success(event, "Event Bot | Created Event").into_string())
})
.or_else(move |_| {
let submit_url = format!("/events/new/{}", id2);
Ok(load_form(
None,
id2,
submit_url,
"Event Bot | New Event",
Some(option_event),
))
}),
)
}
pub fn build(event_handler: EventHandler, prefix: Option<&str>) -> App>
where
T: Actor>
+ Handler
+ Handler
+ Handler
+ Clone,
{
let app = App::with_state(event_handler);
let app = if let Some(prefix) = prefix {
app.prefix(prefix)
} else {
app
};
app.resource("/events/new/{secret}", |r| {
r.method(Method::GET).with(new_form);
r.method(Method::POST).with3(submitted);
}).resource("/events/edit/{secret}", |r| {
r.method(Method::GET).with2(edit_form);
r.method(Method::POST).with3(updated);
})
.handler("/assets/", fs::StaticFiles::new("assets/"))
}
pub fn start(handler: Addr, addr: &str, prefix: Option<&'static str>)
where
T: Actor>
+ Handler
+ Handler
+ Handler
+ Clone,
{
HttpServer::new(move || build(EventHandler::new(handler.clone()), prefix))
.bind(addr)
.unwrap()
.start();
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}