about summary refs log tree commit diff
path: root/web/converse/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/converse/src')
-rw-r--r--web/converse/src/db.rs50
-rw-r--r--web/converse/src/errors.rs17
-rw-r--r--web/converse/src/handlers.rs178
-rw-r--r--web/converse/src/main.rs73
-rw-r--r--web/converse/src/models.rs10
-rw-r--r--web/converse/src/oidc.rs45
-rw-r--r--web/converse/src/render.rs43
-rw-r--r--web/converse/src/schema.rs7
8 files changed, 244 insertions, 179 deletions
diff --git a/web/converse/src/db.rs b/web/converse/src/db.rs
index ae186bdf4e4d..a0d8915504d6 100644
--- a/web/converse/src/db.rs
+++ b/web/converse/src/db.rs
@@ -19,13 +19,13 @@
 //! This module implements the database executor, which holds the
 //! database connection and performs queries on it.
 
+use crate::errors::{ConverseError, Result};
+use crate::models::*;
 use actix::prelude::*;
-use diesel::{self, sql_query};
-use diesel::sql_types::Text;
 use diesel::prelude::*;
-use diesel::r2d2::{Pool, ConnectionManager};
-use crate::models::*;
-use crate::errors::{ConverseError, Result};
+use diesel::r2d2::{ConnectionManager, Pool};
+use diesel::sql_types::Text;
+use diesel::{self, sql_query};
 
 /// Raw PostgreSQL query used to perform full-text search on posts
 /// with a supplied phrase. For now, the query language is hardcoded
@@ -50,14 +50,12 @@ pub struct DbExecutor(pub Pool<ConnectionManager<PgConnection>>);
 
 impl DbExecutor {
     /// Request a list of threads.
-    //
     // TODO(tazjin): This should support pagination.
     pub fn list_threads(&self) -> Result<Vec<ThreadIndex>> {
         use crate::schema::thread_index::dsl::*;
 
         let conn = self.0.get()?;
-        let results = thread_index
-            .load::<ThreadIndex>(&conn)?;
+        let results = thread_index.load::<ThreadIndex>(&conn)?;
         Ok(results)
     }
 
@@ -69,9 +67,7 @@ impl DbExecutor {
 
         let conn = self.0.get()?;
 
-        let opt_user = users
-            .filter(email.eq(email))
-            .first(&conn).optional()?;
+        let opt_user = users.filter(email.eq(email)).first(&conn).optional()?;
 
         if let Some(user) = opt_user {
             Ok(user)
@@ -93,12 +89,11 @@ impl DbExecutor {
 
     /// Fetch a specific thread and return it with its posts.
     pub fn get_thread(&self, thread_id: i32) -> Result<(Thread, Vec<SimplePost>)> {
-        use crate::schema::threads::dsl::*;
         use crate::schema::simple_posts::dsl::id;
+        use crate::schema::threads::dsl::*;
 
         let conn = self.0.get()?;
-        let thread_result: Thread = threads
-            .find(thread_id).first(&conn)?;
+        let thread_result: Thread = threads.find(thread_id).first(&conn)?;
 
         let post_list = SimplePost::belonging_to(&thread_result)
             .order_by(id.asc())
@@ -127,8 +122,7 @@ impl DbExecutor {
 
     /// Create a new thread.
     pub fn create_thread(&self, new_thread: NewThread, post_text: String) -> Result<Thread> {
-                use crate::schema::threads;
-        use crate::schema::posts;
+        use crate::schema::{posts, threads};
 
         let conn = self.0.get()?;
 
@@ -161,20 +155,21 @@ impl DbExecutor {
 
         let closed: bool = {
             use crate::schema::threads::dsl::*;
-            threads.select(closed)
+            threads
+                .select(closed)
                 .find(new_post.thread_id)
                 .first(&conn)?
         };
 
         if closed {
             return Err(ConverseError::ThreadClosed {
-                id: new_post.thread_id
-            })
+                id: new_post.thread_id,
+            });
         }
 
         Ok(diesel::insert_into(posts::table)
-           .values(&new_post)
-           .get_result(&conn)?)
+            .values(&new_post)
+            .get_result(&conn)?)
     }
 
     /// Search for posts.
@@ -197,7 +192,6 @@ impl DbExecutor {
     }
 }
 
-
 // Old actor implementation:
 
 impl Actor for DbExecutor {
@@ -216,9 +210,7 @@ message!(LookupOrCreateUser, Result<User>);
 impl Handler<LookupOrCreateUser> for DbExecutor {
     type Result = <LookupOrCreateUser as Message>::Result;
 
-    fn handle(&mut self,
-              _: LookupOrCreateUser,
-              _: &mut Self::Context) -> Self::Result {
+    fn handle(&mut self, _: LookupOrCreateUser, _: &mut Self::Context) -> Self::Result {
         unimplemented!()
     }
 }
@@ -238,7 +230,9 @@ impl Handler<GetThread> for DbExecutor {
 
 /// Message used to fetch a specific post.
 #[derive(Deserialize, Debug)]
-pub struct GetPost { pub id: i32 }
+pub struct GetPost {
+    pub id: i32,
+}
 
 message!(GetPost, Result<SimplePost>);
 
@@ -296,7 +290,9 @@ impl Handler<CreatePost> for DbExecutor {
 
 /// Message used to search for posts
 #[derive(Deserialize)]
-pub struct SearchPosts { pub query: String }
+pub struct SearchPosts {
+    pub query: String,
+}
 message!(SearchPosts, Result<Vec<SearchResult>>);
 
 impl Handler<SearchPosts> for DbExecutor {
diff --git a/web/converse/src/errors.rs b/web/converse/src/errors.rs
index 32507c51b0c2..a4bd69023b8b 100644
--- a/web/converse/src/errors.rs
+++ b/web/converse/src/errors.rs
@@ -21,17 +21,12 @@
 //! are established in a similar way as was tradition in
 //! `error_chain`, albeit manually.
 
-use std::result;
-use actix_web::{ResponseError, HttpResponse};
 use actix_web::http::StatusCode;
+use actix_web::{HttpResponse, ResponseError};
+use std::result;
 
 // Modules with foreign errors:
-use actix;
-use actix_web;
-use askama;
-use diesel;
-use r2d2;
-use tokio_timer;
+use {actix, actix_web, askama, diesel, r2d2, tokio_timer};
 
 pub type Result<T> = result::Result<T, ConverseError>;
 pub type ConverseResult<T> = result::Result<T, ConverseError>;
@@ -96,7 +91,9 @@ impl From<askama::Error> for ConverseError {
 
 impl From<actix::MailboxError> for ConverseError {
     fn from(error: actix::MailboxError) -> ConverseError {
-        ConverseError::Actix { error: Box::new(error) }
+        ConverseError::Actix {
+            error: Box::new(error),
+        }
     }
 }
 
@@ -136,7 +133,7 @@ impl ResponseError for ConverseError {
                 .header("Location", format!("/thread/{}#post-reply", id))
                 .finish(),
             _ => HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
-                .body(format!("An error occured: {}", self))
+                .body(format!("An error occured: {}", self)),
         }
     }
 }
diff --git a/web/converse/src/handlers.rs b/web/converse/src/handlers.rs
index 0759cec5c146..49f9dcf9745f 100644
--- a/web/converse/src/handlers.rs
+++ b/web/converse/src/handlers.rs
@@ -23,22 +23,22 @@
 //! the tera templates stored in the `/templates` directory in the
 //! project root.
 
-use actix::prelude::*;
-use actix_web::*;
-use actix_web::http::Method;
-use actix_web::middleware::identity::RequestIdentity;
-use actix_web::middleware::{Started, Middleware};
-use actix_web;
 use crate::db::*;
-use crate::errors::{ConverseResult, ConverseError};
-use futures::Future;
+use crate::errors::{ConverseError, ConverseResult};
 use crate::models::*;
 use crate::oidc::*;
 use crate::render::*;
+use actix::prelude::*;
+use actix_web;
+use actix_web::http::Method;
+use actix_web::middleware::identity::RequestIdentity;
+use actix_web::middleware::{Middleware, Started};
+use actix_web::*;
+use futures::Future;
 
 use rouille::{Request, Response};
 
-type ConverseResponse = Box<dyn Future<Item=HttpResponse, Error=ConverseError>>;
+type ConverseResponse = Box<dyn Future<Item = HttpResponse, Error = ConverseError>>;
 
 const HTML: &'static str = "text/html";
 const ANONYMOUS: i32 = 1;
@@ -84,23 +84,31 @@ pub fn get_user_id_rouille(_req: &Request) -> i32 {
     ANONYMOUS
 }
 
-pub fn forum_thread_rouille(req: &Request, db: &DbExecutor, thread_id: i32)
-                            -> ConverseResult<Response> {
+pub fn forum_thread_rouille(
+    req: &Request,
+    db: &DbExecutor,
+    thread_id: i32,
+) -> ConverseResult<Response> {
     let user = get_user_id_rouille(&req);
     let thread = db.get_thread(thread_id)?;
     Ok(Response::html(thread_page(user, thread.0, thread.1)?))
 }
 
 /// This handler retrieves and displays a single forum thread.
-pub fn forum_thread(_: State<AppState>,
-                    _: HttpRequest<AppState>,
-                    _: Path<i32>) -> ConverseResponse {
+pub fn forum_thread(
+    _: State<AppState>,
+    _: HttpRequest<AppState>,
+    _: Path<i32>,
+) -> ConverseResponse {
     unimplemented!()
 }
 
 /// This handler presents the user with the "New Thread" form.
 pub fn new_thread(state: State<AppState>) -> ConverseResponse {
-    state.renderer.send(NewThreadPage::default()).flatten()
+    state
+        .renderer
+        .send(NewThreadPage::default())
+        .flatten()
         .map(|res| HttpResponse::Ok().content_type(HTML).body(res))
         .responder()
 }
@@ -113,9 +121,9 @@ pub struct NewThreadForm {
 
 /// This handler receives a "New thread"-form and redirects the user
 /// to the new thread after creation.
-pub fn submit_thread((state, input, req): (State<AppState>,
-                                           Form<NewThreadForm>,
-                                           HttpRequest<AppState>)) -> ConverseResponse {
+pub fn submit_thread(
+    (state, input, req): (State<AppState>, Form<NewThreadForm>, HttpRequest<AppState>),
+) -> ConverseResponse {
     // Trim whitespace out of inputs:
     let input = NewThreadForm {
         title: input.title.trim().into(),
@@ -124,7 +132,8 @@ pub fn submit_thread((state, input, req): (State<AppState>,
 
     // Perform simple validation and abort here if it fails:
     if input.title.is_empty() || input.post.is_empty() {
-        return state.renderer
+        return state
+            .renderer
             .send(NewThreadPage {
                 alerts: vec![NEW_THREAD_LENGTH_ERR],
                 title: Some(input.title),
@@ -147,14 +156,19 @@ pub fn submit_thread((state, input, req): (State<AppState>,
         post: input.post,
     };
 
-    state.db.send(msg)
+    state
+        .db
+        .send(msg)
         .from_err()
         .and_then(move |res| {
             let thread = res?;
-            info!("Created new thread \"{}\" with ID {}", thread.title, thread.id);
+            info!(
+                "Created new thread \"{}\" with ID {}",
+                thread.title, thread.id
+            );
             Ok(HttpResponse::SeeOther()
-               .header("Location", format!("/thread/{}", thread.id))
-               .finish())
+                .header("Location", format!("/thread/{}", thread.id))
+                .finish())
         })
         .responder()
 }
@@ -167,9 +181,11 @@ pub struct NewPostForm {
 
 /// This handler receives a "Reply"-form and redirects the user to the
 /// new post after creation.
-pub fn reply_thread(state: State<AppState>,
-                    input: Form<NewPostForm>,
-                    req: HttpRequest<AppState>) -> ConverseResponse {
+pub fn reply_thread(
+    state: State<AppState>,
+    input: Form<NewPostForm>,
+    req: HttpRequest<AppState>,
+) -> ConverseResponse {
     let user_id = get_user_id(&req);
 
     let new_post = NewPost {
@@ -178,14 +194,19 @@ pub fn reply_thread(state: State<AppState>,
         body: input.post.trim().into(),
     };
 
-    state.db.send(CreatePost(new_post))
+    state
+        .db
+        .send(CreatePost(new_post))
         .flatten()
         .from_err()
         .and_then(move |post| {
             info!("Posted reply {} to thread {}", post.id, post.thread_id);
             Ok(HttpResponse::SeeOther()
-               .header("Location", format!("/thread/{}#post-{}", post.thread_id, post.id))
-               .finish())
+                .header(
+                    "Location",
+                    format!("/thread/{}#post-{}", post.thread_id, post.id),
+                )
+                .finish())
         })
         .responder()
 }
@@ -194,12 +215,16 @@ pub fn reply_thread(state: State<AppState>,
 /// the user attempts to edit a post that they do not have access to,
 /// they are currently ungracefully redirected back to the post
 /// itself.
-pub fn edit_form(state: State<AppState>,
-                 req: HttpRequest<AppState>,
-                 query: Path<GetPost>) -> ConverseResponse {
+pub fn edit_form(
+    state: State<AppState>,
+    req: HttpRequest<AppState>,
+    query: Path<GetPost>,
+) -> ConverseResponse {
     let user_id = get_user_id(&req);
 
-    state.db.send(query.into_inner())
+    state
+        .db
+        .send(query.into_inner())
         .flatten()
         .from_err()
         .and_then(move |post| {
@@ -227,17 +252,21 @@ pub fn edit_form(state: State<AppState>,
 
 /// This handler "executes" an edit to a post if the current user owns
 /// the edited post.
-pub fn edit_post(state: State<AppState>,
-                 req: HttpRequest<AppState>,
-                 update: Form<UpdatePost>) -> ConverseResponse {
+pub fn edit_post(
+    state: State<AppState>,
+    req: HttpRequest<AppState>,
+    update: Form<UpdatePost>,
+) -> ConverseResponse {
     let user_id = get_user_id(&req);
 
-    state.db.send(GetPost { id: update.post_id })
+    state
+        .db
+        .send(GetPost { id: update.post_id })
         .flatten()
         .from_err()
         .and_then(move |post| {
             if user_id != 1 && post.user_id == user_id {
-                 Ok(())
+                Ok(())
             } else {
                 Err(ConverseError::PostEditForbidden {
                     user: user_id,
@@ -247,24 +276,34 @@ pub fn edit_post(state: State<AppState>,
         })
         .and_then(move |_| state.db.send(update.0).from_err())
         .flatten()
-        .map(|updated| HttpResponse::SeeOther()
-             .header("Location", format!("/thread/{}#post-{}",
-                                         updated.thread_id, updated.id))
-             .finish())
+        .map(|updated| {
+            HttpResponse::SeeOther()
+                .header(
+                    "Location",
+                    format!("/thread/{}#post-{}", updated.thread_id, updated.id),
+                )
+                .finish()
+        })
         .responder()
 }
 
 /// This handler executes a full-text search on the forum database and
 /// displays the results to the user.
-pub fn search_forum(state: State<AppState>,
-                    query: Query<SearchPosts>) -> ConverseResponse {
+pub fn search_forum(state: State<AppState>, query: Query<SearchPosts>) -> ConverseResponse {
     let query_string = query.query.clone();
-    state.db.send(query.into_inner())
+    state
+        .db
+        .send(query.into_inner())
         .flatten()
-        .and_then(move |results| state.renderer.send(SearchResultPage {
-            results,
-            query: query_string,
-        }).from_err())
+        .and_then(move |results| {
+            state
+                .renderer
+                .send(SearchResultPage {
+                    results,
+                    query: query_string,
+                })
+                .from_err()
+        })
         .flatten()
         .map(|res| HttpResponse::Ok().content_type(HTML).body(res))
         .responder()
@@ -272,11 +311,15 @@ pub fn search_forum(state: State<AppState>,
 
 /// This handler initiates an OIDC login.
 pub fn login(state: State<AppState>) -> ConverseResponse {
-    state.oidc.send(GetLoginUrl)
+    state
+        .oidc
+        .send(GetLoginUrl)
         .from_err()
-        .and_then(|url| Ok(HttpResponse::TemporaryRedirect()
-                           .header("Location", url)
-                           .finish()))
+        .and_then(|url| {
+            Ok(HttpResponse::TemporaryRedirect()
+                .header("Location", url)
+                .finish())
+        })
         .responder()
 }
 
@@ -286,21 +329,26 @@ pub fn login(state: State<AppState>) -> ConverseResponse {
 /// provider and a user lookup is performed. If a user with a matching
 /// email-address is found in the database, it is logged in -
 /// otherwise a new user is created.
-pub fn callback(state: State<AppState>,
-                data: Form<CodeResponse>,
-                req: HttpRequest<AppState>) -> ConverseResponse {
-    state.oidc.send(RetrieveToken(data.0)).flatten()
+pub fn callback(
+    state: State<AppState>,
+    data: Form<CodeResponse>,
+    req: HttpRequest<AppState>,
+) -> ConverseResponse {
+    state
+        .oidc
+        .send(RetrieveToken(data.0))
+        .flatten()
         .map(|author| LookupOrCreateUser {
             email: author.email,
             name: author.name,
         })
-        .and_then(move |msg| state.db.send(msg).from_err()).flatten()
+        .and_then(move |msg| state.db.send(msg).from_err())
+        .flatten()
         .and_then(move |user| {
             info!("Completed login for user {} ({})", user.email, user.id);
             req.remember(user.id.to_string());
-            Ok(HttpResponse::SeeOther()
-               .header("Location", "/")
-               .finish())})
+            Ok(HttpResponse::SeeOther().header("Location", "/").finish())
+        })
         .responder()
 }
 
@@ -317,9 +365,7 @@ impl EmbeddedFile for App<AppState> {
     fn static_file(self, path: &'static str, content: &'static [u8]) -> Self {
         self.route(path, Method::GET, move |_: HttpRequest<_>| {
             let mime = format!("{}", mime_guess::from_path(path).first_or_octet_stream());
-            HttpResponse::Ok()
-                .content_type(mime.as_str())
-                .body(content)
+            HttpResponse::Ok().content_type(mime.as_str()).body(content)
         })
     }
 }
@@ -327,7 +373,7 @@ impl EmbeddedFile for App<AppState> {
 /// Middleware used to enforce logins unceremoniously.
 pub struct RequireLogin;
 
-impl <S> Middleware<S> for RequireLogin {
+impl<S> Middleware<S> for RequireLogin {
     fn start(&self, req: &HttpRequest<S>) -> actix_web::Result<Started> {
         let logged_in = req.identity().is_some();
         let is_oidc_req = req.path().starts_with("/oidc");
@@ -336,7 +382,7 @@ impl <S> Middleware<S> for RequireLogin {
             Ok(Started::Response(
                 HttpResponse::SeeOther()
                     .header("Location", "/oidc/login")
-                    .finish()
+                    .finish(),
             ))
         } else {
             Ok(Started::Done)
diff --git a/web/converse/src/main.rs b/web/converse/src/main.rs
index 6d6e9ac71020..78d0241600b4 100644
--- a/web/converse/src/main.rs
+++ b/web/converse/src/main.rs
@@ -30,7 +30,6 @@ extern crate log;
 #[macro_use]
 extern crate serde_derive;
 
-extern crate rouille;
 extern crate actix;
 extern crate actix_web;
 extern crate chrono;
@@ -44,6 +43,7 @@ extern crate md5;
 extern crate mime_guess;
 extern crate r2d2;
 extern crate rand;
+extern crate rouille;
 extern crate serde;
 extern crate serde_json;
 extern crate tokio;
@@ -58,7 +58,7 @@ macro_rules! message {
         impl Message for $t {
             type Result = $r;
         }
-    }
+    };
 }
 
 pub mod db;
@@ -69,18 +69,18 @@ pub mod oidc;
 pub mod render;
 pub mod schema;
 
+use crate::db::*;
+use crate::handlers::*;
+use crate::oidc::OidcExecutor;
+use crate::render::Renderer;
 use actix::prelude::*;
-use actix_web::*;
 use actix_web::http::Method;
+use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
 use actix_web::middleware::Logger;
-use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
-use crate::db::*;
+use actix_web::*;
 use diesel::pg::PgConnection;
 use diesel::r2d2::{ConnectionManager, Pool};
-use crate::handlers::*;
-use crate::oidc::OidcExecutor;
 use rand::{OsRng, Rng};
-use crate::render::Renderer;
 use std::env;
 
 fn config(name: &str) -> String {
@@ -96,16 +96,18 @@ fn start_db_executor() -> Addr<DbExecutor> {
     let db_url = config("DATABASE_URL");
 
     let manager = ConnectionManager::<PgConnection>::new(db_url);
-    let pool = Pool::builder().build(manager).expect("Failed to initialise DB pool");
+    let pool = Pool::builder()
+        .build(manager)
+        .expect("Failed to initialise DB pool");
 
     SyncArbiter::start(2, move || DbExecutor(pool.clone()))
 }
 
 fn schedule_search_refresh(db: Addr<DbExecutor>) {
+    use std::thread;
+    use std::time::{Duration, Instant};
     use tokio::prelude::*;
     use tokio::timer::Interval;
-    use std::time::{Duration, Instant};
-    use std::thread;
 
     let task = Interval::new(Instant::now(), Duration::from_secs(60))
         .from_err()
@@ -118,8 +120,8 @@ fn schedule_search_refresh(db: Addr<DbExecutor>) {
 fn start_oidc_executor(base_url: &str) -> Addr<OidcExecutor> {
     info!("Initialising OIDC integration ...");
     let oidc_url = config("OIDC_DISCOVERY_URL");
-    let oidc_config = oidc::load_oidc(&oidc_url)
-        .expect("Failed to retrieve OIDC discovery document");
+    let oidc_config =
+        oidc::load_oidc(&oidc_url).expect("Failed to retrieve OIDC discovery document");
 
     let oidc = oidc::OidcExecutor {
         oidc_config,
@@ -132,7 +134,7 @@ fn start_oidc_executor(base_url: &str) -> Addr<OidcExecutor> {
 }
 
 fn start_renderer() -> Addr<Renderer> {
-    let comrak = comrak::ComrakOptions{
+    let comrak = comrak::ComrakOptions {
         github_pre_lang: true,
         ext_strikethrough: true,
         ext_table: true,
@@ -143,22 +145,23 @@ fn start_renderer() -> Addr<Renderer> {
         ..Default::default()
     };
 
-    Renderer{ comrak }.start()
+    Renderer { comrak }.start()
 }
 
 fn gen_session_key() -> [u8; 64] {
     let mut key_bytes = [0; 64];
-    let mut rng = OsRng::new()
-        .expect("Failed to retrieve RNG for key generation");
+    let mut rng = OsRng::new().expect("Failed to retrieve RNG for key generation");
     rng.fill_bytes(&mut key_bytes);
 
     key_bytes
 }
 
-fn start_http_server(base_url: String,
-                     db_addr: Addr<DbExecutor>,
-                     oidc_addr: Addr<OidcExecutor>,
-                     renderer_addr: Addr<Renderer>) {
+fn start_http_server(
+    base_url: String,
+    db_addr: Addr<DbExecutor>,
+    oidc_addr: Addr<OidcExecutor>,
+    renderer_addr: Addr<Renderer>,
+) {
     info!("Initialising HTTP server ...");
     let bind_host = config_default("CONVERSE_BIND_HOST", "127.0.0.1:4567");
     let key = gen_session_key();
@@ -175,7 +178,7 @@ fn start_http_server(base_url: String,
             CookieIdentityPolicy::new(&key)
                 .name("converse_auth")
                 .path("/")
-                .secure(base_url.starts_with("https"))
+                .secure(base_url.starts_with("https")),
         );
 
         let app = App::with_state(state)
@@ -183,25 +186,37 @@ fn start_http_server(base_url: String,
             .middleware(identity)
             .resource("/", |r| r.method(Method::GET).with(forum_index))
             .resource("/thread/new", |r| r.method(Method::GET).with(new_thread))
-            .resource("/thread/submit", |r| r.method(Method::POST).with(submit_thread))
-            .resource("/thread/reply", |r| r.method(Method::POST).with(reply_thread))
+            .resource("/thread/submit", |r| {
+                r.method(Method::POST).with(submit_thread)
+            })
+            .resource("/thread/reply", |r| {
+                r.method(Method::POST).with(reply_thread)
+            })
             .resource("/thread/{id}", |r| r.method(Method::GET).with(forum_thread))
             .resource("/post/{id}/edit", |r| r.method(Method::GET).with(edit_form))
             .resource("/post/edit", |r| r.method(Method::POST).with(edit_post))
             .resource("/search", |r| r.method(Method::GET).with(search_forum))
             .resource("/oidc/login", |r| r.method(Method::GET).with(login))
             .resource("/oidc/callback", |r| r.method(Method::POST).with(callback))
-            .static_file("/static/highlight.css", include_bytes!("../static/highlight.css"))
-            .static_file("/static/highlight.js", include_bytes!("../static/highlight.js"))
+            .static_file(
+                "/static/highlight.css",
+                include_bytes!("../static/highlight.css"),
+            )
+            .static_file(
+                "/static/highlight.js",
+                include_bytes!("../static/highlight.js"),
+            )
             .static_file("/static/styles.css", include_bytes!("../static/styles.css"));
 
         if require_login {
             app.middleware(RequireLogin)
         } else {
             app
-        }})
-        .bind(&bind_host).expect(&format!("Could not bind on '{}'", bind_host))
-        .start();
+        }
+    })
+    .bind(&bind_host)
+    .expect(&format!("Could not bind on '{}'", bind_host))
+    .start();
 }
 
 fn main() {
diff --git a/web/converse/src/models.rs b/web/converse/src/models.rs
index da628f78b5bc..63b15fbed061 100644
--- a/web/converse/src/models.rs
+++ b/web/converse/src/models.rs
@@ -16,9 +16,9 @@
 // along with this program. If not, see
 // <https://www.gnu.org/licenses/>.
 
+use crate::schema::{posts, simple_posts, threads, users};
 use chrono::prelude::{DateTime, Utc};
-use crate::schema::{users, threads, posts, simple_posts};
-use diesel::sql_types::{Text, Integer};
+use diesel::sql_types::{Integer, Text};
 
 /// Represents a single user in the Converse database. Converse does
 /// not handle logins itself, but rather looks them up based on the
@@ -85,21 +85,21 @@ pub struct ThreadIndex {
 }
 
 #[derive(Deserialize, Insertable)]
-#[table_name="threads"]
+#[table_name = "threads"]
 pub struct NewThread {
     pub title: String,
     pub user_id: i32,
 }
 
 #[derive(Deserialize, Insertable)]
-#[table_name="users"]
+#[table_name = "users"]
 pub struct NewUser {
     pub email: String,
     pub name: String,
 }
 
 #[derive(Deserialize, Insertable)]
-#[table_name="posts"]
+#[table_name = "posts"]
 pub struct NewPost {
     pub thread_id: i32,
     pub body: String,
diff --git a/web/converse/src/oidc.rs b/web/converse/src/oidc.rs
index 9f566c04a71a..75e3eabc88f2 100644
--- a/web/converse/src/oidc.rs
+++ b/web/converse/src/oidc.rs
@@ -22,12 +22,12 @@
 //! Currently Converse only supports a single OIDC provider. Note that
 //! this has so far only been tested with Office365.
 
-use actix::prelude::*;
 use crate::errors::*;
+use actix::prelude::*;
 use crimp::Request;
+use curl::easy::Form;
 use url::Url;
 use url_serde;
-use curl::easy::Form;
 
 /// This structure represents the contents of an OIDC discovery
 /// document.
@@ -114,20 +114,30 @@ impl Handler<RetrieveToken> for OidcExecutor {
         debug!("Received OAuth2 code, requesting access_token");
 
         let mut form = Form::new();
-        form.part("client_id").contents(&self.client_id.as_bytes())
-            .add().expect("critical error: invalid form data");
-
-        form.part("client_secret").contents(&self.client_secret.as_bytes())
-            .add().expect("critical error: invalid form data");
-
-        form.part("grant_type").contents("authorization_code".as_bytes())
-            .add().expect("critical error: invalid form data");
-
-        form.part("code").contents(&msg.0.code.as_bytes())
-            .add().expect("critical error: invalid form data");
-
-        form.part("redirect_uri").contents(&self.redirect_uri.as_bytes())
-            .add().expect("critical error: invalid form data");
+        form.part("client_id")
+            .contents(&self.client_id.as_bytes())
+            .add()
+            .expect("critical error: invalid form data");
+
+        form.part("client_secret")
+            .contents(&self.client_secret.as_bytes())
+            .add()
+            .expect("critical error: invalid form data");
+
+        form.part("grant_type")
+            .contents("authorization_code".as_bytes())
+            .add()
+            .expect("critical error: invalid form data");
+
+        form.part("code")
+            .contents(&msg.0.code.as_bytes())
+            .add()
+            .expect("critical error: invalid form data");
+
+        form.part("redirect_uri")
+            .contents(&self.redirect_uri.as_bytes())
+            .add()
+            .expect("critical error: invalid form data");
 
         let response = Request::post(&self.oidc_config.token_endpoint)
             .user_agent(concat!("converse-", env!("CARGO_PKG_VERSION")))?
@@ -142,7 +152,8 @@ impl Handler<RetrieveToken> for OidcExecutor {
             .user_agent(concat!("converse-", env!("CARGO_PKG_VERSION")))?
             .header("Authorization", &bearer)?
             .send()?
-            .as_json()?.body;
+            .as_json()?
+            .body;
 
         Ok(Author {
             name: user.name,
diff --git a/web/converse/src/render.rs b/web/converse/src/render.rs
index 749e77ef50eb..d06af12bd9f1 100644
--- a/web/converse/src/render.rs
+++ b/web/converse/src/render.rs
@@ -20,14 +20,14 @@
 //! data into whatever format is needed by the templates and rendering
 //! them.
 
-use actix::prelude::*;
-use askama::Template;
 use crate::errors::*;
-use std::fmt;
-use md5;
 use crate::models::*;
+use actix::prelude::*;
+use askama::Template;
 use chrono::prelude::{DateTime, Utc};
-use comrak::{ComrakOptions, markdown_to_html};
+use comrak::{markdown_to_html, ComrakOptions};
+use md5;
+use std::fmt;
 
 pub struct Renderer {
     pub comrak: ComrakOptions,
@@ -101,7 +101,9 @@ pub enum EditingMode {
 }
 
 impl Default for EditingMode {
-    fn default() -> EditingMode { EditingMode::NewThread }
+    fn default() -> EditingMode {
+        EditingMode::NewThread
+    }
 }
 
 /// This is the template used for rendering the new thread, edit post
@@ -215,19 +217,22 @@ pub fn index_page(threads: Vec<ThreadIndex>) -> Result<String> {
 
 // Render the page of a given thread.
 pub fn thread_page(user: i32, thread: Thread, posts: Vec<SimplePost>) -> Result<String> {
-    let posts = posts.into_iter().map(|post| {
-        let editable = user != 1 && post.user_id == user;
-
-        let comrak = ComrakOptions::default(); // TODO(tazjin): cheddar
-        RenderablePost {
-            id: post.id,
-            body: markdown_to_html(&post.body, &comrak),
-            posted: FormattedDate(post.posted),
-            author_name: post.author_name.clone(),
-            author_gravatar: md5_hex(post.author_email.as_bytes()),
-            editable,
-        }
-    }).collect();
+    let posts = posts
+        .into_iter()
+        .map(|post| {
+            let editable = user != 1 && post.user_id == user;
+
+            let comrak = ComrakOptions::default(); // TODO(tazjin): cheddar
+            RenderablePost {
+                id: post.id,
+                body: markdown_to_html(&post.body, &comrak),
+                posted: FormattedDate(post.posted),
+                author_name: post.author_name.clone(),
+                author_gravatar: md5_hex(post.author_email.as_bytes()),
+                editable,
+            }
+        })
+        .collect();
 
     let renderable = RenderableThreadPage {
         posts,
diff --git a/web/converse/src/schema.rs b/web/converse/src/schema.rs
index 7de6d13668c2..520af4342261 100644
--- a/web/converse/src/schema.rs
+++ b/web/converse/src/schema.rs
@@ -80,9 +80,4 @@ joinable!(posts -> users (user_id));
 joinable!(threads -> users (user_id));
 joinable!(simple_posts -> threads (thread_id));
 
-allow_tables_to_appear_in_same_query!(
-    posts,
-    threads,
-    users,
-    simple_posts,
-);
+allow_tables_to_appear_in_same_query!(posts, threads, users, simple_posts,);