diff --git a/web/converse/Cargo.lock b/web/converse/Cargo.lock
index 351ab1d26..b022bdd55 100644
--- a/web/converse/Cargo.lock
+++ b/web/converse/Cargo.lock
@@ -81,8 +81,8 @@ dependencies = [
"lazy_static",
"lazycell",
"log 0.4.14",
- "mime",
- "mime_guess",
+ "mime 0.3.16",
+ "mime_guess 2.0.3",
"mio",
"net2",
"num_cpus",
@@ -134,6 +134,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+[[package]]
+name = "adler32"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
+
[[package]]
name = "aead"
version = "0.3.2"
@@ -206,6 +212,24 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "ascii"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97be891acc47ca214468e09425d02cef3af2c94d0d82081cd02061f996802f14"
+
[[package]]
name = "askama"
version = "0.6.4"
@@ -311,6 +335,17 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+[[package]]
+name = "blake2b_simd"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "constant_time_eq",
+]
+
[[package]]
name = "block-buffer"
version = "0.9.0"
@@ -340,6 +375,16 @@ dependencies = [
"libc",
]
+[[package]]
+name = "buf_redux"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
+dependencies = [
+ "memchr 2.3.4",
+ "safemem",
+]
+
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -388,6 +433,12 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "chunked_transfer"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87"
+
[[package]]
name = "cipher"
version = "0.2.5"
@@ -438,6 +489,12 @@ dependencies = [
"unicode_categories",
]
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
[[package]]
name = "converse"
version = "0.1.0"
@@ -456,11 +513,12 @@ dependencies = [
"hyper",
"log 0.4.14",
"md5",
- "mime_guess",
+ "mime_guess 2.0.3",
"pq-sys",
"pulldown-cmark",
"r2d2",
"rand 0.4.6",
+ "rouille",
"serde",
"serde_derive",
"serde_json",
@@ -585,6 +643,17 @@ dependencies = [
"lazy_static",
]
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
+dependencies = [
+ "autocfg 1.0.1",
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
[[package]]
name = "crypto-mac"
version = "0.10.0"
@@ -634,6 +703,17 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "deflate"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
+dependencies = [
+ "adler32",
+ "byteorder",
+ "gzip-header",
+]
+
[[package]]
name = "diesel"
version = "1.4.6"
@@ -668,6 +748,17 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "dirs"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi 0.3.9",
+]
+
[[package]]
name = "dtoa"
version = "0.4.8"
@@ -797,6 +888,18 @@ dependencies = [
"synstructure",
]
+[[package]]
+name = "filetime"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall 0.2.5",
+ "winapi 0.3.9",
+]
+
[[package]]
name = "flate2"
version = "1.0.20"
@@ -873,6 +976,17 @@ dependencies = [
"unicode-width",
]
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
[[package]]
name = "getrandom"
version = "0.2.2"
@@ -881,7 +995,7 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if 1.0.0",
"libc",
- "wasi",
+ "wasi 0.10.0+wasi-snapshot-preview1",
]
[[package]]
@@ -900,6 +1014,15 @@ version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
+[[package]]
+name = "gzip-header"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0131feb3d3bb2a5a238d8a4d09f6353b7ebfdc52e77bccbf4ea6eaa751dde639"
+dependencies = [
+ "crc32fast",
+]
+
[[package]]
name = "h2"
version = "0.1.26"
@@ -1004,7 +1127,7 @@ dependencies = [
"iovec",
"language-tags",
"log 0.4.14",
- "mime",
+ "mime 0.3.16",
"net2",
"percent-encoding 1.0.1",
"relay",
@@ -1013,7 +1136,7 @@ dependencies = [
"tokio-io",
"tokio-proto",
"tokio-service",
- "unicase",
+ "unicase 2.6.0",
"want",
]
@@ -1230,20 +1353,41 @@ dependencies = [
"autocfg 1.0.1",
]
+[[package]]
+name = "mime"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
+dependencies = [
+ "log 0.3.9",
+]
+
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+[[package]]
+name = "mime_guess"
+version = "1.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3"
+dependencies = [
+ "mime 0.2.6",
+ "phf",
+ "phf_codegen",
+ "unicase 1.4.2",
+]
+
[[package]]
name = "mime_guess"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
dependencies = [
- "mime",
- "unicase",
+ "mime 0.3.16",
+ "unicase 2.6.0",
]
[[package]]
@@ -1308,6 +1452,24 @@ dependencies = [
"ws2_32-sys",
]
+[[package]]
+name = "multipart"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adba94490a79baf2d6a23eac897157047008272fa3eecb3373ae6377b91eca28"
+dependencies = [
+ "buf_redux",
+ "httparse",
+ "log 0.4.14",
+ "mime 0.2.6",
+ "mime_guess 1.8.8",
+ "quick-error",
+ "rand 0.4.6",
+ "safemem",
+ "tempdir",
+ "twoway",
+]
+
[[package]]
name = "net2"
version = "0.2.37"
@@ -1510,6 +1672,45 @@ dependencies = [
"syn 0.11.11",
]
+[[package]]
+name = "phf"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
+dependencies = [
+ "phf_shared",
+ "rand 0.6.5",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
+dependencies = [
+ "siphasher",
+ "unicase 1.4.2",
+]
+
[[package]]
name = "pkg-config"
version = "0.3.19"
@@ -1737,7 +1938,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
dependencies = [
- "getrandom",
+ "getrandom 0.2.2",
]
[[package]]
@@ -1835,6 +2036,17 @@ dependencies = [
"bitflags 1.2.1",
]
+[[package]]
+name = "redox_users"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
+dependencies = [
+ "getrandom 0.1.16",
+ "redox_syscall 0.1.57",
+ "rust-argon2",
+]
+
[[package]]
name = "regex"
version = "1.4.5"
@@ -1861,6 +2073,15 @@ dependencies = [
"futures",
]
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi 0.3.9",
+]
+
[[package]]
name = "resolv-conf"
version = "0.6.3"
@@ -1871,6 +2092,43 @@ dependencies = [
"quick-error",
]
+[[package]]
+name = "rouille"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112568052ec17fa26c6c11c40acbb30d3ad244bf3d6da0be181f5e7e42e5004f"
+dependencies = [
+ "base64 0.9.3",
+ "brotli2",
+ "chrono",
+ "deflate",
+ "filetime",
+ "multipart",
+ "num_cpus",
+ "rand 0.5.6",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sha1",
+ "term",
+ "threadpool",
+ "time",
+ "tiny_http",
+ "url",
+]
+
+[[package]]
+name = "rust-argon2"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
+dependencies = [
+ "base64 0.13.0",
+ "blake2b_simd",
+ "constant_time_eq",
+ "crossbeam-utils 0.8.3",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.18"
@@ -2018,6 +2276,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "siphasher"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
+
[[package]]
name = "slab"
version = "0.3.0"
@@ -2160,6 +2424,27 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
+[[package]]
+name = "tempdir"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
+dependencies = [
+ "rand 0.4.6",
+ "remove_dir_all",
+]
+
+[[package]]
+name = "term"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42"
+dependencies = [
+ "byteorder",
+ "dirs",
+ "winapi 0.3.9",
+]
+
[[package]]
name = "termcolor"
version = "1.1.2"
@@ -2179,15 +2464,38 @@ dependencies = [
]
[[package]]
-name = "time"
-version = "0.1.43"
+name = "threadpool"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
"winapi 0.3.9",
]
+[[package]]
+name = "tiny_http"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1661fa0a44c95d01604bd05c66732a446c657efb62b5164a7a083a3b552b4951"
+dependencies = [
+ "ascii",
+ "chrono",
+ "chunked_transfer",
+ "log 0.4.14",
+ "url",
+]
+
[[package]]
name = "tinyvec"
version = "1.2.0"
@@ -2551,6 +2859,15 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+[[package]]
+name = "unicase"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
+dependencies = [
+ "version_check 0.1.5",
+]
+
[[package]]
name = "unicase"
version = "2.6.0"
@@ -2717,9 +3034,15 @@ dependencies = [
[[package]]
name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
+version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "widestring"
diff --git a/web/converse/Cargo.toml b/web/converse/Cargo.toml
index c49f1c4c6..c77101144 100644
--- a/web/converse/Cargo.toml
+++ b/web/converse/Cargo.toml
@@ -30,6 +30,7 @@ tokio-timer = "0.2"
url = "1.7"
url_serde = "0.2"
curl = "*" # bounded by crimp
+rouille = "3.0"
[build-dependencies]
pulldown-cmark = "0.1"
diff --git a/web/converse/src/db.rs b/web/converse/src/db.rs
index 1d6159589..ae186bdf4 100644
--- a/web/converse/src/db.rs
+++ b/web/converse/src/db.rs
@@ -16,7 +16,8 @@
// along with this program. If not, see
// .
-//! This module implements the database connection actor.
+//! This module implements the database executor, which holds the
+//! database connection and performs queries on it.
use actix::prelude::*;
use diesel::{self, sql_query};
@@ -26,212 +27,6 @@ use diesel::r2d2::{Pool, ConnectionManager};
use crate::models::*;
use crate::errors::{ConverseError, Result};
-/// The DB actor itself. Several of these will be run in parallel by
-/// `SyncArbiter`.
-pub struct DbExecutor(pub Pool>);
-
-impl Actor for DbExecutor {
- type Context = SyncContext;
-}
-
-/// Message used to request a list of threads.
-/// TODO: This should support page numbers.
-pub struct ListThreads;
-message!(ListThreads, Result>);
-
-impl Handler for DbExecutor {
- type Result = ::Result;
-
- fn handle(&mut self, _: ListThreads, _: &mut Self::Context) -> Self::Result {
- use crate::schema::thread_index::dsl::*;
-
- let conn = self.0.get()?;
- let results = thread_index
- .load::(&conn)?;
- Ok(results)
- }
-}
-
-/// Message used to look up a user based on their email-address. If
-/// the user does not exist, it is created.
-pub struct LookupOrCreateUser {
- pub email: String,
- pub name: String,
-}
-
-message!(LookupOrCreateUser, Result);
-
-impl Handler for DbExecutor {
- type Result = ::Result;
-
- fn handle(&mut self,
- msg: LookupOrCreateUser,
- _: &mut Self::Context) -> Self::Result {
- use crate::schema::users;
- use crate::schema::users::dsl::*;
-
- let conn = self.0.get()?;
-
- let opt_user = users
- .filter(email.eq(&msg.email))
- .first(&conn).optional()?;
-
- if let Some(user) = opt_user {
- Ok(user)
- } else {
- let new_user = NewUser {
- email: msg.email,
- name: msg.name,
- };
-
- let user: User = diesel::insert_into(users::table)
- .values(&new_user)
- .get_result(&conn)?;
-
- info!("Created new user {} with ID {}", new_user.email, user.id);
-
- Ok(user)
- }
- }
-}
-
-/// Message used to fetch a specific thread. Returns the thread and
-/// its posts.
-pub struct GetThread(pub i32);
-message!(GetThread, Result<(Thread, Vec)>);
-
-impl Handler for DbExecutor {
- type Result = ::Result;
-
- fn handle(&mut self, msg: GetThread, _: &mut Self::Context) -> Self::Result {
- use crate::schema::threads::dsl::*;
- use crate::schema::simple_posts::dsl::id;
-
- let conn = self.0.get()?;
- let thread_result: Thread = threads
- .find(msg.0).first(&conn)?;
-
- let post_list = SimplePost::belonging_to(&thread_result)
- .order_by(id.asc())
- .load::(&conn)?;
-
- Ok((thread_result, post_list))
- }
-}
-
-/// Message used to fetch a specific post.
-#[derive(Deserialize, Debug)]
-pub struct GetPost { pub id: i32 }
-
-message!(GetPost, Result);
-
-impl Handler for DbExecutor {
- type Result = ::Result;
-
- fn handle(&mut self, msg: GetPost, _: &mut Self::Context) -> Self::Result {
- use crate::schema::simple_posts::dsl::*;
- let conn = self.0.get()?;
- Ok(simple_posts.find(msg.id).first(&conn)?)
- }
-}
-
-/// Message used to update the content of a post.
-#[derive(Deserialize)]
-pub struct UpdatePost {
- pub post_id: i32,
- pub post: String,
-}
-
-message!(UpdatePost, Result);
-
-impl Handler for DbExecutor {
- type Result = Result;
-
- fn handle(&mut self, msg: UpdatePost, _: &mut Self::Context) -> Self::Result {
- use crate::schema::posts::dsl::*;
- let conn = self.0.get()?;
- let updated = diesel::update(posts.find(msg.post_id))
- .set(body.eq(msg.post))
- .get_result(&conn)?;
-
- Ok(updated)
- }
-}
-
-/// Message used to create a new thread
-pub struct CreateThread {
- pub new_thread: NewThread,
- pub post: String,
-}
-message!(CreateThread, Result);
-
-impl Handler for DbExecutor {
- type Result = ::Result;
-
- fn handle(&mut self, msg: CreateThread, _: &mut Self::Context) -> Self::Result {
- use crate::schema::threads;
- use crate::schema::posts;
-
- let conn = self.0.get()?;
-
- conn.transaction::(|| {
- // First insert the thread structure itself
- let thread: Thread = diesel::insert_into(threads::table)
- .values(&msg.new_thread)
- .get_result(&conn)?;
-
- // ... then create the first post in the thread.
- let new_post = NewPost {
- thread_id: thread.id,
- body: msg.post,
- user_id: msg.new_thread.user_id,
- };
-
- diesel::insert_into(posts::table)
- .values(&new_post)
- .execute(&conn)?;
-
- Ok(thread)
- })
- }
-}
-
-/// Message used to create a new reply
-pub struct CreatePost(pub NewPost);
-message!(CreatePost, Result);
-
-impl Handler for DbExecutor {
- type Result = ::Result;
-
- fn handle(&mut self, msg: CreatePost, _: &mut Self::Context) -> Self::Result {
- use crate::schema::posts;
-
- let conn = self.0.get()?;
-
- let closed: bool = {
- use crate::schema::threads::dsl::*;
- threads.select(closed)
- .find(msg.0.thread_id)
- .first(&conn)?
- };
-
- if closed {
- return Err(ConverseError::ThreadClosed {
- id: msg.0.thread_id
- })
- }
-
- Ok(diesel::insert_into(posts::table)
- .values(&msg.0)
- .get_result(&conn)?)
- }
-}
-
-/// Message used to search for posts
-#[derive(Deserialize)]
-pub struct SearchPosts { pub query: String }
-message!(SearchPosts, Result>);
-
/// Raw PostgreSQL query used to perform full-text search on posts
/// with a supplied phrase. For now, the query language is hardcoded
/// to English and only "plain" queries (i.e. no searches for exact
@@ -249,18 +44,267 @@ SELECT post_id,
LIMIT 50
"#;
-impl Handler for DbExecutor {
- type Result = ::Result;
+const REFRESH_QUERY: &'static str = "REFRESH MATERIALIZED VIEW search_index";
- fn handle(&mut self, msg: SearchPosts, _: &mut Self::Context) -> Self::Result {
+pub struct DbExecutor(pub Pool>);
+
+impl DbExecutor {
+ /// Request a list of threads.
+ //
+ // TODO(tazjin): This should support pagination.
+ pub fn list_threads(&self) -> Result> {
+ use crate::schema::thread_index::dsl::*;
+
+ let conn = self.0.get()?;
+ let results = thread_index
+ .load::(&conn)?;
+ Ok(results)
+ }
+
+ /// Look up a user based on their email-address. If the user does
+ /// not exist, it is created.
+ pub fn lookup_or_create_user(&self, user_email: &str, user_name: &str) -> Result {
+ use crate::schema::users;
+ use crate::schema::users::dsl::*;
+
+ let conn = self.0.get()?;
+
+ let opt_user = users
+ .filter(email.eq(email))
+ .first(&conn).optional()?;
+
+ if let Some(user) = opt_user {
+ Ok(user)
+ } else {
+ let new_user = NewUser {
+ email: user_email.to_string(),
+ name: user_name.to_string(),
+ };
+
+ let user: User = diesel::insert_into(users::table)
+ .values(&new_user)
+ .get_result(&conn)?;
+
+ info!("Created new user {} with ID {}", new_user.email, user.id);
+
+ Ok(user)
+ }
+ }
+
+ /// Fetch a specific thread and return it with its posts.
+ pub fn get_thread(&self, thread_id: i32) -> Result<(Thread, Vec)> {
+ use crate::schema::threads::dsl::*;
+ use crate::schema::simple_posts::dsl::id;
+
+ let conn = self.0.get()?;
+ let thread_result: Thread = threads
+ .find(thread_id).first(&conn)?;
+
+ let post_list = SimplePost::belonging_to(&thread_result)
+ .order_by(id.asc())
+ .load::(&conn)?;
+
+ Ok((thread_result, post_list))
+ }
+
+ /// Fetch a specific post.
+ pub fn get_post(&self, post_id: i32) -> Result {
+ use crate::schema::simple_posts::dsl::*;
+ let conn = self.0.get()?;
+ Ok(simple_posts.find(post_id).first(&conn)?)
+ }
+
+ /// Update the content of a post.
+ pub fn update_post(&self, post_id: i32, post_text: String) -> Result {
+ use crate::schema::posts::dsl::*;
+ let conn = self.0.get()?;
+ let updated = diesel::update(posts.find(post_id))
+ .set(body.eq(post_text))
+ .get_result(&conn)?;
+
+ Ok(updated)
+ }
+
+ /// Create a new thread.
+ pub fn create_thread(&self, new_thread: NewThread, post_text: String) -> Result {
+ use crate::schema::threads;
+ use crate::schema::posts;
+
+ let conn = self.0.get()?;
+
+ conn.transaction::(|| {
+ // First insert the thread structure itself
+ let thread: Thread = diesel::insert_into(threads::table)
+ .values(&new_thread)
+ .get_result(&conn)?;
+
+ // ... then create the first post in the thread.
+ let new_post = NewPost {
+ thread_id: thread.id,
+ body: post_text,
+ user_id: new_thread.user_id,
+ };
+
+ diesel::insert_into(posts::table)
+ .values(&new_post)
+ .execute(&conn)?;
+
+ Ok(thread)
+ })
+ }
+
+ /// Create a new post.
+ pub fn create_post(&self, new_post: NewPost) -> Result {
+ use crate::schema::posts;
+
+ let conn = self.0.get()?;
+
+ let closed: bool = {
+ use crate::schema::threads::dsl::*;
+ threads.select(closed)
+ .find(new_post.thread_id)
+ .first(&conn)?
+ };
+
+ if closed {
+ return Err(ConverseError::ThreadClosed {
+ id: new_post.thread_id
+ })
+ }
+
+ Ok(diesel::insert_into(posts::table)
+ .values(&new_post)
+ .get_result(&conn)?)
+ }
+
+ /// Search for posts.
+ pub fn search_posts(&self, query: String) -> Result> {
let conn = self.0.get()?;
let search_results = sql_query(SEARCH_QUERY)
- .bind::(msg.query)
+ .bind::(query)
.get_results::(&conn)?;
Ok(search_results)
}
+
+ /// Trigger a refresh of the view used for full-text searching.
+ pub fn refresh_search_view(&self) -> Result<()> {
+ let conn = self.0.get()?;
+ debug!("Refreshing search_index view in DB");
+ sql_query(REFRESH_QUERY).execute(&conn)?;
+ Ok(())
+ }
+}
+
+
+// Old actor implementation:
+
+impl Actor for DbExecutor {
+ type Context = SyncContext;
+}
+
+/// Message used to look up a user based on their email-address. If
+/// the user does not exist, it is created.
+pub struct LookupOrCreateUser {
+ pub email: String,
+ pub name: String,
+}
+
+message!(LookupOrCreateUser, Result);
+
+impl Handler for DbExecutor {
+ type Result = ::Result;
+
+ fn handle(&mut self,
+ _: LookupOrCreateUser,
+ _: &mut Self::Context) -> Self::Result {
+ unimplemented!()
+ }
+}
+
+/// Message used to fetch a specific thread. Returns the thread and
+/// its posts.
+pub struct GetThread(pub i32);
+message!(GetThread, Result<(Thread, Vec)>);
+
+impl Handler for DbExecutor {
+ type Result = ::Result;
+
+ fn handle(&mut self, _: GetThread, _: &mut Self::Context) -> Self::Result {
+ unimplemented!()
+ }
+}
+
+/// Message used to fetch a specific post.
+#[derive(Deserialize, Debug)]
+pub struct GetPost { pub id: i32 }
+
+message!(GetPost, Result);
+
+impl Handler for DbExecutor {
+ type Result = ::Result;
+
+ fn handle(&mut self, _: GetPost, _: &mut Self::Context) -> Self::Result {
+ unimplemented!()
+ }
+}
+
+/// Message used to update the content of a post.
+#[derive(Deserialize)]
+pub struct UpdatePost {
+ pub post_id: i32,
+ pub post: String,
+}
+
+message!(UpdatePost, Result);
+
+impl Handler for DbExecutor {
+ type Result = Result;
+
+ fn handle(&mut self, _: UpdatePost, _: &mut Self::Context) -> Self::Result {
+ unimplemented!()
+ }
+}
+
+/// Message used to create a new thread
+pub struct CreateThread {
+ pub new_thread: NewThread,
+ pub post: String,
+}
+message!(CreateThread, Result);
+
+impl Handler for DbExecutor {
+ type Result = ::Result;
+
+ fn handle(&mut self, _: CreateThread, _: &mut Self::Context) -> Self::Result {
+ unimplemented!()
+ }
+}
+
+/// Message used to create a new reply
+pub struct CreatePost(pub NewPost);
+message!(CreatePost, Result);
+
+impl Handler for DbExecutor {
+ type Result = ::Result;
+
+ fn handle(&mut self, _: CreatePost, _: &mut Self::Context) -> Self::Result {
+ unimplemented!()
+ }
+}
+
+/// Message used to search for posts
+#[derive(Deserialize)]
+pub struct SearchPosts { pub query: String }
+message!(SearchPosts, Result>);
+
+impl Handler for DbExecutor {
+ type Result = ::Result;
+
+ fn handle(&mut self, _: SearchPosts, _: &mut Self::Context) -> Self::Result {
+ unimplemented!()
+ }
}
/// Message that triggers a refresh of the view used for full-text
@@ -268,15 +312,10 @@ impl Handler for DbExecutor {
pub struct RefreshSearchView;
message!(RefreshSearchView, Result<()>);
-const REFRESH_QUERY: &'static str = "REFRESH MATERIALIZED VIEW search_index";
-
impl Handler for DbExecutor {
type Result = Result<()>;
fn handle(&mut self, _: RefreshSearchView, _: &mut Self::Context) -> Self::Result {
- let conn = self.0.get()?;
- debug!("Refreshing search_index view in DB");
- sql_query(REFRESH_QUERY).execute(&conn)?;
- Ok(())
+ unimplemented!()
}
}
diff --git a/web/converse/src/errors.rs b/web/converse/src/errors.rs
index b079f41c4..32507c51b 100644
--- a/web/converse/src/errors.rs
+++ b/web/converse/src/errors.rs
@@ -34,6 +34,7 @@ use r2d2;
use tokio_timer;
pub type Result = result::Result;
+pub type ConverseResult = result::Result;
#[derive(Debug, Fail)]
pub enum ConverseError {
diff --git a/web/converse/src/handlers.rs b/web/converse/src/handlers.rs
index c4448c748..0759cec5c 100644
--- a/web/converse/src/handlers.rs
+++ b/web/converse/src/handlers.rs
@@ -30,12 +30,14 @@ use actix_web::middleware::identity::RequestIdentity;
use actix_web::middleware::{Started, Middleware};
use actix_web;
use crate::db::*;
-use crate::errors::ConverseError;
+use crate::errors::{ConverseResult, ConverseError};
use futures::Future;
use crate::models::*;
use crate::oidc::*;
use crate::render::*;
+use rouille::{Request, Response};
+
type ConverseResponse = Box>;
const HTML: &'static str = "text/html";
@@ -54,15 +56,14 @@ pub struct AppState {
pub renderer: Addr,
}
-pub fn forum_index(state: State) -> ConverseResponse {
- state.db.send(ListThreads)
- .flatten()
- .and_then(move |res| state.renderer.send(IndexPage {
- threads: res
- }).from_err())
- .flatten()
- .map(|res| HttpResponse::Ok().content_type(HTML).body(res))
- .responder()
+/// Serve the forum's index page.
+pub fn forum_index_rouille(db: &DbExecutor) -> ConverseResult {
+ let threads = db.list_threads()?;
+ Ok(Response::html(index_page(threads)?))
+}
+
+pub fn forum_index(_: State) -> ConverseResponse {
+ unimplemented!()
}
/// Returns the ID of the currently logged in user. If there is no ID
@@ -78,23 +79,23 @@ pub fn get_user_id(req: &HttpRequest) -> i32 {
}
}
-/// This handler retrieves and displays a single forum thread.
-pub fn forum_thread(state: State,
- req: HttpRequest,
- thread_id: Path) -> ConverseResponse {
- let id = thread_id.into_inner();
- let user = get_user_id(&req);
+pub fn get_user_id_rouille(_req: &Request) -> i32 {
+ // TODO(tazjin): Implement session support in rouille somehow.
+ ANONYMOUS
+}
- state.db.send(GetThread(id))
- .flatten()
- .and_then(move |res| state.renderer.send(ThreadPage {
- current_user: user,
- thread: res.0,
- posts: res.1,
- }).from_err())
- .flatten()
- .map(|res| HttpResponse::Ok().content_type(HTML).body(res))
- .responder()
+pub fn forum_thread_rouille(req: &Request, db: &DbExecutor, thread_id: i32)
+ -> ConverseResult {
+ 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,
+ _: HttpRequest,
+ _: Path) -> ConverseResponse {
+ unimplemented!()
}
/// This handler presents the user with the "New Thread" form.
diff --git a/web/converse/src/main.rs b/web/converse/src/main.rs
index d17626c17..6d6e9ac71 100644
--- a/web/converse/src/main.rs
+++ b/web/converse/src/main.rs
@@ -30,6 +30,7 @@ extern crate log;
#[macro_use]
extern crate serde_derive;
+extern crate rouille;
extern crate actix;
extern crate actix_web;
extern crate chrono;
diff --git a/web/converse/src/render.rs b/web/converse/src/render.rs
index cfc08377a..749e77ef5 100644
--- a/web/converse/src/render.rs
+++ b/web/converse/src/render.rs
@@ -47,12 +47,6 @@ impl fmt::Display for FormattedDate {
}
}
-/// Message used to render the index page.
-pub struct IndexPage {
- pub threads: Vec,
-}
-message!(IndexPage, Result);
-
#[derive(Debug)]
struct IndexThread {
id: i32,
@@ -70,39 +64,6 @@ struct IndexPageTemplate {
threads: Vec,
}
-impl Handler for Renderer {
- type Result = Result;
-
- fn handle(&mut self, msg: IndexPage, _: &mut Self::Context) -> Self::Result {
- let threads: Vec = msg.threads
- .into_iter()
- .map(|thread| IndexThread {
- id: thread.thread_id,
- title: thread.title, // escape_html(&thread.title),
- sticky: thread.sticky,
- closed: thread.closed,
- posted: FormattedDate(thread.posted),
- author_name: thread.thread_author,
- post_author: thread.post_author,
- })
- .collect();
-
- let tpl = IndexPageTemplate {
- threads
- };
-
- tpl.render().map_err(|e| e.into())
- }
-}
-
-/// Message used to render a thread.
-pub struct ThreadPage {
- pub current_user: i32,
- pub thread: Thread,
- pub posts: Vec,
-}
-message!(ThreadPage, Result);
-
// "Renderable" structures with data transformations applied.
#[derive(Debug)]
struct RenderablePost {
@@ -130,39 +91,6 @@ fn md5_hex(input: &[u8]) -> String {
format!("{:x}", md5::compute(input))
}
-fn prepare_thread(comrak: &ComrakOptions, page: ThreadPage) -> RenderableThreadPage {
- let user = page.current_user;
-
- let posts = page.posts.into_iter().map(|post| {
- let editable = user != 1 && post.user_id == user;
-
- 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();
-
- RenderableThreadPage {
- posts,
- closed: page.thread.closed,
- id: page.thread.id,
- title: page.thread.title,
- }
-}
-
-impl Handler for Renderer {
- type Result = Result;
-
- fn handle(&mut self, msg: ThreadPage, _: &mut Self::Context) -> Self::Result {
- let renderable = prepare_thread(&self.comrak, msg);
- renderable.render().map_err(|e| e.into())
- }
-}
-
/// The different types of editing modes supported by the editing
/// template:
#[derive(Debug, PartialEq)]
@@ -263,3 +191,50 @@ impl Handler for Renderer {
msg.render().map_err(|e| e.into())
}
}
+
+// TODO: actor-free implementation below
+
+/// Render the index page for the given thread list.
+pub fn index_page(threads: Vec) -> Result {
+ let threads: Vec = threads
+ .into_iter()
+ .map(|thread| IndexThread {
+ id: thread.thread_id,
+ title: thread.title, // escape_html(&thread.title),
+ sticky: thread.sticky,
+ closed: thread.closed,
+ posted: FormattedDate(thread.posted),
+ author_name: thread.thread_author,
+ post_author: thread.post_author,
+ })
+ .collect();
+
+ let tpl = IndexPageTemplate { threads };
+ tpl.render().map_err(|e| e.into())
+}
+
+// Render the page of a given thread.
+pub fn thread_page(user: i32, thread: Thread, posts: Vec) -> Result {
+ 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,
+ closed: thread.closed,
+ id: thread.id,
+ title: thread.title,
+ };
+
+ Ok(renderable.render()?)
+}