treewide: Migrate to the new dream2nix API
This commit is contained in:
parent
5163ef9d6a
commit
40e0946201
59 changed files with 15003 additions and 24504 deletions
4
packages/server/.dir-locals.el
Normal file
4
packages/server/.dir-locals.el
Normal file
|
@ -0,0 +1,4 @@
|
|||
((rust-mode . ((indent-tabs-mode . nil)
|
||||
(tab-width . 4)
|
||||
(fill-column . 80)
|
||||
(projectile-project-run-cmd . "cd server && cargo run -- --dev-mode --template-directory ../templates/result"))))
|
1515
packages/server/Cargo.lock
generated
Normal file
1515
packages/server/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
15
packages/server/Cargo.toml
Normal file
15
packages/server/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "tlaternet-webserver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix-files = "0.6.2"
|
||||
actix-web = { version = "4.2.1", features = ["macros"] }
|
||||
clap = { version = "3.2.17", features = ["derive"] }
|
||||
derive_more = "0.99.17"
|
||||
env_logger = "0.9.0"
|
||||
handlebars = { version = "4.3.3", features = ["dir_source"] }
|
||||
log = "0.4.17"
|
||||
serde = {version = "1.0.144", features = ["derive"]}
|
||||
serde_json = "1.0.83"
|
17
packages/server/default.nix
Normal file
17
packages/server/default.nix
Normal file
|
@ -0,0 +1,17 @@
|
|||
{dream2nix, ...}: {
|
||||
imports = [
|
||||
dream2nix.modules.dream2nix.rust-cargo-lock
|
||||
dream2nix.modules.dream2nix.rust-crane
|
||||
];
|
||||
|
||||
deps = {fenix, ...}: {
|
||||
deps.cargo = fenix.stable.minimalToolchain;
|
||||
};
|
||||
|
||||
name = "tlaternet-webserver";
|
||||
version = "0.1.0";
|
||||
|
||||
mkDerivation = {
|
||||
src = ./.;
|
||||
};
|
||||
}
|
72
packages/server/src/errors.rs
Normal file
72
packages/server/src/errors.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use actix_web::body::BoxBody;
|
||||
use actix_web::dev::ServiceResponse;
|
||||
use actix_web::http::header::ContentType;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::middleware::ErrorHandlerResponse;
|
||||
use actix_web::{web, HttpResponse, ResponseError};
|
||||
use derive_more::{Display, Error};
|
||||
|
||||
use super::SharedData;
|
||||
use crate::template_utils::{render_template, ErrorMessage, TemplateArgs};
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
pub enum UserError {
|
||||
NotFound,
|
||||
#[display(fmt = "Internal error. Try again later.")]
|
||||
InternalError,
|
||||
}
|
||||
|
||||
impl ResponseError for UserError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::build(self.status_code())
|
||||
.insert_header(ContentType::html())
|
||||
.body(self.to_string())
|
||||
}
|
||||
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
UserError::NotFound => StatusCode::NOT_FOUND,
|
||||
UserError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generic_error<B>(
|
||||
res: ServiceResponse<B>,
|
||||
) -> actix_web::Result<ErrorHandlerResponse<BoxBody>> {
|
||||
let data = res
|
||||
.request()
|
||||
.app_data::<web::Data<SharedData>>()
|
||||
.map(|t| t.get_ref());
|
||||
|
||||
let status_code = res.response().status();
|
||||
let message = if let Some(error) = status_code.canonical_reason() {
|
||||
error
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let response = match data {
|
||||
Some(SharedData {
|
||||
handlebars,
|
||||
config: _,
|
||||
}) => {
|
||||
let args = TemplateArgs::builder()
|
||||
.error_page(ErrorMessage::new(message, status_code.as_u16()))
|
||||
.build();
|
||||
|
||||
let body = render_template(handlebars, "error", &args)
|
||||
.map_err(|_| UserError::InternalError)?;
|
||||
|
||||
HttpResponse::build(res.status())
|
||||
.content_type(ContentType::html())
|
||||
.body(body)
|
||||
}
|
||||
None => Err(UserError::InternalError)?,
|
||||
};
|
||||
|
||||
Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
|
||||
res.into_parts().0,
|
||||
response.map_into_left_body(),
|
||||
)))
|
||||
}
|
99
packages/server/src/main.rs
Normal file
99
packages/server/src/main.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
#![allow(dead_code)]
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use actix_files::Files;
|
||||
use actix_web::{
|
||||
http::{Method, StatusCode},
|
||||
middleware::{self, ErrorHandlers},
|
||||
web, App, HttpServer,
|
||||
};
|
||||
use clap::Parser;
|
||||
use env_logger::{Env, WriteStyle};
|
||||
use handlebars::Handlebars;
|
||||
use log::LevelFilter;
|
||||
|
||||
mod errors;
|
||||
mod main_pages;
|
||||
mod template_utils;
|
||||
|
||||
use errors::generic_error;
|
||||
use main_pages::{mail_post, template};
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
struct Config {
|
||||
#[clap(long, value_parser)]
|
||||
/// The directory from which to serve static content and
|
||||
/// handlebars templates
|
||||
template_directory: PathBuf,
|
||||
#[clap(long, default_value = "127.0.0.1:8000", value_parser)]
|
||||
/// The address on which to listen
|
||||
address: SocketAddr,
|
||||
#[clap(long, action)]
|
||||
/// Whether to start the server in dev mode; this enables some nice
|
||||
/// handlebars features that are not intended for production, and enables
|
||||
/// more verbose logs
|
||||
dev_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SharedData<'a> {
|
||||
handlebars: Handlebars<'a>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let config = {
|
||||
let mut config = Config::parse();
|
||||
config.template_directory = config.template_directory.canonicalize()?;
|
||||
config
|
||||
};
|
||||
|
||||
env_logger::Builder::new()
|
||||
.filter_level(if config.dev_mode {
|
||||
LevelFilter::Info
|
||||
} else {
|
||||
LevelFilter::Debug
|
||||
})
|
||||
.write_style(WriteStyle::Always)
|
||||
.parse_env(
|
||||
Env::new()
|
||||
.filter("TLATERNET_LOG_LEVEL")
|
||||
.write_style("TLATERNET_LOG_STYLE"),
|
||||
)
|
||||
.init();
|
||||
|
||||
let mut handlebars = Handlebars::new();
|
||||
handlebars
|
||||
.register_templates_directory(".html", config.template_directory.clone())
|
||||
.expect("templates should compile correctly");
|
||||
handlebars.set_dev_mode(config.dev_mode);
|
||||
|
||||
let shared_data = web::Data::new(SharedData {
|
||||
handlebars,
|
||||
config: config.clone(),
|
||||
});
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(middleware::NormalizePath::trim())
|
||||
// TODO(tlater): When actix-web 4.3 releases, this can be improved a
|
||||
// lot because of this PR:
|
||||
//
|
||||
// https://github.com/actix/actix-web/pull/2784
|
||||
.wrap(
|
||||
ErrorHandlers::new()
|
||||
.handler(StatusCode::NOT_FOUND, generic_error)
|
||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, generic_error),
|
||||
)
|
||||
.app_data(shared_data.clone())
|
||||
.service(template)
|
||||
.service(mail_post)
|
||||
.service(Files::new("/", &config.template_directory))
|
||||
.default_service(web::route().method(Method::GET))
|
||||
})
|
||||
.bind(config.address)?
|
||||
.run()
|
||||
.await
|
||||
}
|
45
packages/server/src/main_pages.rs
Normal file
45
packages/server/src/main_pages.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use actix_web::{post, routes, web, HttpRequest, HttpResponse, Responder};
|
||||
use log::info;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::template_utils::{render_template, Flash, FlashType, TemplateArgs};
|
||||
use crate::SharedData;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub(crate) struct Mail {
|
||||
mail: String,
|
||||
subject: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[routes]
|
||||
#[get(r"/")]
|
||||
#[get(r"/{filename:.*\.html}")]
|
||||
pub(crate) async fn template(
|
||||
shared: web::Data<SharedData<'_>>,
|
||||
req: HttpRequest,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let path = match req.match_info().query("filename") {
|
||||
"" => "index",
|
||||
other => other
|
||||
.strip_suffix(".html")
|
||||
.expect("only paths with this suffix should get here"),
|
||||
};
|
||||
|
||||
render_template(&shared.handlebars, path, &TemplateArgs::default())
|
||||
.map(|body| HttpResponse::Ok().body(body))
|
||||
}
|
||||
|
||||
#[post("/mail.html")]
|
||||
pub(crate) async fn mail_post(
|
||||
shared: web::Data<SharedData<'_>>,
|
||||
form: web::Form<Mail>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
info!("{:?}", form);
|
||||
|
||||
let args = TemplateArgs::builder()
|
||||
.flash(Flash::new("Mail successfully sent!", FlashType::Success))
|
||||
.build();
|
||||
|
||||
render_template(&shared.handlebars, "mail", &args).map(|body| HttpResponse::Ok().body(body))
|
||||
}
|
105
packages/server/src/template_utils.rs
Normal file
105
packages/server/src/template_utils.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use log::error;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::errors::UserError;
|
||||
|
||||
pub fn render_template(
|
||||
handlebars: &handlebars::Handlebars,
|
||||
name: &str,
|
||||
args: &TemplateArgs,
|
||||
) -> actix_web::Result<String> {
|
||||
if handlebars.has_template(name) {
|
||||
Ok(handlebars
|
||||
.render(name, args)
|
||||
.map_err(|_| UserError::InternalError)?)
|
||||
} else {
|
||||
error!("template not found: {}", name);
|
||||
Err(UserError::NotFound)?
|
||||
}
|
||||
}
|
||||
|
||||
/** All arguments that can be given to a template. */
|
||||
#[derive(Default, Serialize)]
|
||||
pub struct TemplateArgs {
|
||||
flash: Option<Flash>,
|
||||
error: Option<ErrorMessage>,
|
||||
}
|
||||
|
||||
impl TemplateArgs {
|
||||
pub fn builder() -> TemplateArgsBuilder {
|
||||
TemplateArgsBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TemplateArgsBuilder {
|
||||
flash: Option<Flash>,
|
||||
error: Option<ErrorMessage>,
|
||||
}
|
||||
|
||||
impl TemplateArgsBuilder {
|
||||
pub fn new() -> Self {
|
||||
TemplateArgsBuilder {
|
||||
flash: None,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flash(mut self, flash: Flash) -> Self {
|
||||
self.flash = Some(flash);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn error_page(mut self, error: ErrorMessage) -> Self {
|
||||
self.error = Some(error);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> TemplateArgs {
|
||||
TemplateArgs {
|
||||
flash: self.flash,
|
||||
error: self.error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A flash message that should be displayed as a notification on the page. */
|
||||
#[derive(Serialize)]
|
||||
pub struct Flash {
|
||||
message: String,
|
||||
#[serde(rename = "type")]
|
||||
level: FlashType,
|
||||
}
|
||||
|
||||
impl Flash {
|
||||
pub fn new(message: &str, level: FlashType) -> Self {
|
||||
Self {
|
||||
message: message.to_string(),
|
||||
level,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum FlashType {
|
||||
Info,
|
||||
Success,
|
||||
Warning,
|
||||
Danger,
|
||||
}
|
||||
|
||||
/** Contents of an error page. */
|
||||
#[derive(Serialize)]
|
||||
pub struct ErrorMessage {
|
||||
message: String,
|
||||
status_code: u16,
|
||||
}
|
||||
|
||||
impl ErrorMessage {
|
||||
pub fn new(message: &str, status_code: u16) -> Self {
|
||||
Self {
|
||||
message: message.to_string(),
|
||||
status_code,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue