style(rust): Format all Rust code with rustfmt
Change-Id: Iab7e00cc26a4f9727d3ab98691ef379921a33052 Reviewed-on: https://cl.tvl.fyi/c/depot/+/5240 Tested-by: BuildkiteCI Reviewed-by: kanepyork <rikingcoding@gmail.com> Reviewed-by: Profpatsch <mail@profpatsch.de> Reviewed-by: grfn <grfn@gws.fyi> Reviewed-by: tazjin <tazjin@tvl.su>
This commit is contained in:
parent
3318982f81
commit
3d8ee62087
42 changed files with 1253 additions and 876 deletions
|
|
@ -28,14 +28,19 @@ fn main() {
|
|||
|
||||
// Otherwise ask Nix to build it and inject the result.
|
||||
let output = Command::new("nix-build")
|
||||
.arg("-A").arg("web.atward.indexHtml")
|
||||
.arg("-A")
|
||||
.arg("web.atward.indexHtml")
|
||||
// ... assuming atward is at //web/atward ...
|
||||
.arg("../..")
|
||||
.output()
|
||||
.expect(ERROR_MESSAGE);
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("{}\nNix output: {}", ERROR_MESSAGE, String::from_utf8_lossy(&output.stderr));
|
||||
eprintln!(
|
||||
"{}\nNix output: {}",
|
||||
ERROR_MESSAGE,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@
|
|||
//! This module implements the database executor, which holds the
|
||||
//! database connection and performs queries on it.
|
||||
|
||||
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 crate::models::*;
|
||||
use actix::prelude::*;
|
||||
use diesel::prelude::*;
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 actix::prelude::*;
|
||||
use actix_web::*;
|
||||
use actix_web::http::Method;
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
|
||||
use crate::db::*;
|
||||
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 actix::prelude::*;
|
||||
use actix_web::http::Method;
|
||||
use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::*;
|
||||
use diesel::pg::PgConnection;
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use rand::{OsRng, Rng};
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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_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("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("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("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("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,
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@
|
|||
//! data into whatever format is needed by the templates and rendering
|
||||
//! them.
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::models::*;
|
||||
use actix::prelude::*;
|
||||
use askama::Template;
|
||||
use crate::errors::*;
|
||||
use std::fmt;
|
||||
use md5;
|
||||
use crate::models::*;
|
||||
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 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 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,
|
||||
|
|
|
|||
|
|
@ -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,);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue