diff --git a/config/default.toml b/config/default.toml new file mode 100644 index 0000000..cc5aff7 --- /dev/null +++ b/config/default.toml @@ -0,0 +1,14 @@ +debug = true +source_code = "https://github.com/realaravinth/libmedium" + +[server] +# The port at which you want authentication to listen to +# takes a number, choose from 1000-10000 if you dont know what you are doing +port = 7000 +#IP address. Enter 0.0.0.0 to listen on all availale addresses +ip= "0.0.0.0" +# enter your hostname, eg: example.com +domain = "localhost" +allow_registration = true +proxy_has_tls = false +#workers = 2 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..816b88b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use std::env; + +use actix_web::{ + error::InternalError, http::StatusCode, middleware as actix_middleware, web::JsonConfig, App, + HttpServer, +}; +use lazy_static::lazy_static; +use log::info; + +mod meta; +mod proxy; +mod routes; +mod settings; + +pub use routes::ROUTES as V1_API_ROUTES; +pub use settings::Settings; + +lazy_static! { + pub static ref SETTINGS: Settings = Settings::new().unwrap(); +} + +pub const CACHE_AGE: u32 = 604800; + +pub const GIT_COMMIT_HASH: &str = env!("GIT_HASH"); +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const PKG_NAME: &str = env!("CARGO_PKG_NAME"); +pub const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); +pub const PKG_HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE"); + +#[cfg(not(tarpaulin_include))] +#[actix_web::main] +async fn main() -> std::io::Result<()> { + env::set_var("RUST_LOG", "info"); + + pretty_env_logger::init(); + + info!( + "{}: {}.\nFor more information, see: {}\nBuild info:\nVersion: {} commit: {}", + PKG_NAME, PKG_DESCRIPTION, PKG_HOMEPAGE, VERSION, GIT_COMMIT_HASH + ); + + println!("Starting server on: http://{}", SETTINGS.server.get_ip()); + + HttpServer::new(move || { + App::new() + .wrap(actix_middleware::Logger::default()) + .wrap(actix_middleware::Compress::default()) + .app_data(get_json_err()) + .wrap( + actix_middleware::DefaultHeaders::new() + .header("Permissions-Policy", "interest-cohort=()"), + ) + .wrap(actix_middleware::NormalizePath::new( + actix_middleware::TrailingSlash::Trim, + )) + .configure(services) + }) + .workers(SETTINGS.server.workers.unwrap_or_else(num_cpus::get)) + .bind(SETTINGS.server.get_ip()) + .unwrap() + .run() + .await +} + +#[cfg(not(tarpaulin_include))] +pub fn get_json_err() -> JsonConfig { + JsonConfig::default().error_handler(|err, _| { + //debug!("JSON deserialization error: {:?}", &err); + InternalError::new(err, StatusCode::BAD_REQUEST).into() + }) +} + +pub fn services(cfg: &mut actix_web::web::ServiceConfig) { + routes::services(cfg); +} diff --git a/src/meta.rs b/src/meta.rs new file mode 100644 index 0000000..8d25b78 --- /dev/null +++ b/src/meta.rs @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use actix_web::{web, HttpResponse, Responder}; +use serde::{Deserialize, Serialize}; + +use crate::{GIT_COMMIT_HASH, VERSION}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BuildDetails { + pub version: &'static str, + pub git_commit_hash: &'static str, +} + +pub mod routes { + pub struct Meta { + pub build_details: &'static str, + pub health: &'static str, + } + + impl Meta { + pub const fn new() -> Self { + Self { + build_details: "/api/v1/meta/build", + health: "/api/v1/meta/health", + } + } + } +} + +/// emmits build details of the bninary +#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")] +async fn build_details() -> impl Responder { + let build = BuildDetails { + version: VERSION, + git_commit_hash: GIT_COMMIT_HASH, + }; + HttpResponse::Ok().json(build) +} + +pub fn services(cfg: &mut web::ServiceConfig) { + cfg.service(build_details); +} + +#[cfg(test)] +mod tests { + use actix_web::{http::StatusCode, test, App}; + + use crate::services; + use crate::*; + + #[actix_rt::test] + async fn build_details_works() { + let app = test::init_service(App::new().configure(services)).await; + + let resp = test::call_service( + &app, + test::TestRequest::get() + .uri(V1_API_ROUTES.meta.build_details) + .to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); + } +} diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..389440f --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use std::env; +use std::path::Path; + +use config::{Config, ConfigError, Environment, File}; +use log::warn; +use serde::Deserialize; +use url::Url; + +#[derive(Debug, Clone, Deserialize)] +pub struct Server { + pub port: u32, + pub domain: String, + pub ip: String, + pub proxy_has_tls: bool, + pub workers: Option, +} + +impl Server { + #[cfg(not(tarpaulin_include))] + pub fn get_ip(&self) -> String { + format!("{}:{}", self.ip, self.port) + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Settings { + pub debug: bool, + // pub database: Database, + pub server: Server, + pub source_code: String, +} + +#[cfg(not(tarpaulin_include))] +impl Settings { + pub fn new() -> Result { + let mut s = Config::new(); + + // setting default values + #[cfg(test)] + s.set_default("database.pool", 2.to_string()) + .expect("Couldn't get the number of CPUs"); + + const CURRENT_DIR: &str = "./config/default.toml"; + const ETC: &str = "/etc/static-pages/config.toml"; + + if let Ok(path) = env::var("ATHENA_CONFIG") { + s.merge(File::with_name(&path))?; + } else if Path::new(CURRENT_DIR).exists() { + // merging default config from file + s.merge(File::with_name(CURRENT_DIR))?; + } else if Path::new(ETC).exists() { + s.merge(File::with_name(ETC))?; + } else { + log::warn!("configuration file not found"); + } + + s.merge(Environment::with_prefix("PAGES").separator("__"))?; + + check_url(&s); + + match env::var("PORT") { + Ok(val) => { + s.set("server.port", val).unwrap(); + } + Err(e) => warn!("couldn't interpret PORT: {}", e), + } + + let settings: Settings = s.try_into()?; + + Ok(settings) + } +} + +#[cfg(not(tarpaulin_include))] +fn check_url(s: &Config) { + let url = s + .get::("source_code") + .expect("Couldn't access source_code"); + + Url::parse(&url).expect("Please enter a URL for source_code in settings"); +}