/* * 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); } }