#[cfg(feature = "actix-files")]
use actix_files::Files;
use actix_web::{middleware, web, App, HttpServer};
use handlebars::Handlebars;
mod config;
use config::{read_dsn, Config};
mod pages;
use pages::atom::games::atom_games;
use pages::atom::turns::atom_turns;
use pages::cookies_policy::{cookies_policy, post_accept_cookies, post_remove_cookies};
use pages::index::index;
use pages::log_in::{get_log_in, post_log_in, post_log_in_mastodon};
use pages::log_out::log_out;
use pages::my::my;
use pages::query_reset_game_pwd::post_query_reset_game_pwd;
use pages::query_reset_game_pwd::query_reset_game_pwd;
use pages::reset_game_pwd::get_reset_game_pwd;
use pages::reset_game_pwd::post_reset_game_pwd;
use pages::slow_game::{
post_delegate, post_join_game, post_leave_game, post_query_delegate, post_revoke_delegate,
slow_game,
};
use pages::slow_games::slow_games;
use pages::templates;
use pages::well_known;
use pages::{DataBaseRo, DataBaseRw, WebData};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
let args = clap::Command::new("FreeOrion Test Web")
.version("0.1.0")
.author("O01eg <o01eg@yandex.ru>")
.arg(
clap::Arg::new("config")
.short('c')
.long("config")
.value_name("CONFIG")
.help("File with configuration")
.action(clap::ArgAction::Set)
.required(true),
)
.get_matches();
let config = Config::read(
args.get_one::<String>("config")
.expect("Mandatory option config"),
)?;
let Config {
http,
dsn_ro_path,
dsn_rw_path,
dsn_conn,
cache_capacity,
cache_duration_sec,
base_proto,
base_domain,
cookies_key_base64,
set_cookie_domain,
mail,
xmpp_url,
freeorion_version,
#[cfg(feature = "actix-files")]
public,
} = config;
use base64::Engine;
let cookies_key = if let Some(c) = cookies_key_base64 {
match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(c) {
Ok(b) => {
if b.len() < 64 {
log::error!("Small key for cookies");
return Err(std::io::Error::new(std::io::ErrorKind::Other, "Small key"));
} else {
actix_web::cookie::Key::from(&b)
}
}
Err(e) => {
log::error!("Incorrect key for cookies {}", e);
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Incorrect key",
));
}
}
} else {
log::error!("Missing key for cookies");
if let Some(nc) = actix_web::cookie::Key::try_generate() {
log::info!(
"Put line `cookies_key_base64 = \"{}\"` to config",
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(nc.master())
);
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Missing key",
));
} else {
log::error!("Cann't generate key for cookies");
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Cann't generate key",
));
}
};
let pg_ro_config = read_dsn(dsn_ro_path)?;
let pg_rw_config = read_dsn(dsn_rw_path)?;
let mgr_ro = deadpool_postgres::Manager::from_config(
pg_ro_config,
tokio_postgres::NoTls,
deadpool_postgres::ManagerConfig {
recycling_method: deadpool_postgres::RecyclingMethod::Fast,
},
);
let mgr_rw = deadpool_postgres::Manager::from_config(
pg_rw_config,
tokio_postgres::NoTls,
deadpool_postgres::ManagerConfig {
recycling_method: deadpool_postgres::RecyclingMethod::Fast,
},
);
let pool_ro = deadpool_postgres::Pool::builder(mgr_ro)
.max_size(dsn_conn)
.build()
.expect("Postgresql RO pool");
let pool_rw = deadpool_postgres::Pool::builder(mgr_rw)
.max_size(dsn_conn)
.build()
.expect("Postgresql RW pool");
let mut handlebars = Handlebars::new();
handlebars.set_strict_mode(true);
handlebars
.register_template_string(
templates::sub::FOOTER,
include_str!("templates/sub/footer.html"),
)
.expect("footer sub template");
handlebars
.register_template_string(
templates::sub::HEADER,
include_str!("templates/sub/header.html"),
)
.expect("header sub template");
handlebars
.register_template_string(templates::INDEX, include_str!("templates/index.html"))
.expect("index template");
handlebars
.register_template_string(templates::MY, include_str!("templates/my.html"))
.expect("my template");
handlebars
.register_template_string(
templates::SLOW_GAMES,
include_str!("templates/slow-games.html"),
)
.expect("slow games template");
handlebars
.register_template_string(
templates::RESET_GAME_PWD,
include_str!("templates/reset-game-pwd.html"),
)
.expect("reset game pwd template");
handlebars
.register_template_string(
templates::QUERY_RESET_GAME_PWD,
include_str!("templates/query-reset-game-pwd.html"),
)
.expect("query reset game pwd template");
handlebars
.register_template_string(
templates::COOKIES_POLICY,
include_str!("templates/cookies-policy.html"),
)
.expect("cookies policy template");
handlebars
.register_template_string(templates::LOG_IN, include_str!("templates/login.html"))
.expect("cookies policy template");
let mut handlebars_xml = Handlebars::new();
handlebars_xml.set_strict_mode(true);
handlebars_xml.register_escape_fn(handlebars::no_escape);
handlebars_xml
.register_template_string(templates::ATOM_GAMES, include_str!("templates/games.xml"))
.expect("atom games template");
handlebars_xml
.register_template_string(templates::ATOM_TURNS, include_str!("templates/turns.xml"))
.expect("atom games template");
handlebars_xml
.register_template_string(
templates::SLOW_GAME,
include_str!("templates/slow-game.html"),
)
.expect("slow game template");
handlebars_xml
.register_template_string(
templates::sub::FOOTER,
include_str!("templates/sub/footer.html"),
)
.expect("footer sub template");
handlebars_xml
.register_template_string(
templates::sub::HEADER,
include_str!("templates/sub/header.html"),
)
.expect("header sub template");
let smtp_credentials =
lettre::transport::smtp::authentication::Credentials::new(mail.login, mail.passwd);
let data_ref = web::Data::new(WebData {
handlebars,
handlebars_xml,
cache_reset_game_pwd: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
cache_query_reset_game_pwd: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(
cache_capacity,
)),
cache_login: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
cache_join_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
cache_leave_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
cache_query_delegation_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(
cache_capacity,
)),
cache_revoke_delegation_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(
cache_capacity,
)),
cache_delegation_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
cache_duration_sec,
base_proto,
base_domain,
mailer: lettre::AsyncSmtpTransport::<lettre::Tokio1Executor>::relay(&mail.server)
.expect("Mail relay server")
.credentials(smtp_credentials)
.build(),
mailer_from: lettre::message::Mailbox::new(
None,
mail.from
.parse::<lettre::address::Address>()
.expect("Mail from address"),
),
xmpp_url,
cookies_key,
set_cookie_domain,
freeorion_version,
});
let data_ref_ro = web::Data::new(DataBaseRo(pool_ro));
let data_ref_rw = web::Data::new(DataBaseRw(pool_rw));
#[cfg(feature = "non-proxied")]
log::info!("Listen {}", http);
HttpServer::new(move || {
#[cfg(feature = "non-proxied")]
let logger = middleware::Logger::default();
#[cfg(not(feature = "non-proxied"))]
let logger =
middleware::Logger::new("%{r}a \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %T");
#[allow(clippy::let_and_return)]
let app = App::new()
.wrap(logger)
.app_data(data_ref.clone())
.app_data(data_ref_ro.clone())
.app_data(data_ref_rw.clone())
.route("/", web::get().to(index))
.route("/index.html", web::get().to(index))
.route("/my.html", web::get().to(my))
.route("/slow-games.html", web::get().to(slow_games))
.route("/cookies-policy.html", web::get().to(cookies_policy))
.route("/login.html", web::get().to(get_log_in))
.route("/accept-cookies.do", web::post().to(post_accept_cookies))
.route("/remove-cookies.do", web::post().to(post_remove_cookies))
.route("/logout.do", web::post().to(log_out))
.route("/login.do", web::post().to(post_log_in))
.route("/login-mastodon.do", web::post().to(post_log_in_mastodon))
.route("/join-game.do", web::post().to(post_join_game))
.route("/leave-game.do", web::post().to(post_leave_game))
.route("/revoke-delegate.do", web::post().to(post_revoke_delegate))
.route("/query-delegate.do", web::post().to(post_query_delegate))
.route("/delegate.do", web::post().to(post_delegate))
.route(
"/query-reset-game-pwd.html",
web::get().to(query_reset_game_pwd),
)
.route(
"/.well-known/host-meta",
web::get().to(well_known::host_meta),
)
.route("/nodeinfo/2.1", web::get().to(well_known::nodeinfo21))
.route("/.well-known/nodeinfo", web::get().to(well_known::nodeinfo))
.service(post_query_reset_game_pwd)
.service(get_reset_game_pwd)
.service(post_reset_game_pwd)
.service(slow_game)
.service(atom_games)
.service(atom_turns)
.service(well_known::webfinger);
#[cfg(feature = "actix-files")]
let app = app.service(Files::new("/static/", &public));
app
})
.bind(http)?
.run()
.await
}