Commit 04a1e9dc authored by Robert Czechowski's avatar Robert Czechowski
Browse files

Complete Cargo.toml, add features (closes #35), refactor config and options into config module

parent 9bd2ddd2
Pipeline #191 passed with stage
in 15 minutes and 58 seconds
[package] [package]
name = "medal" name = "medal"
version = "0.1.0" version = "0.1.0"
authors = ["Robert Czechowski <czechowski@bwinf.de>"] authors = ["Robert Czechowski <czechowski@bwinf.de>", "Daniel Brüning <bruening@bwinf.de>"]
description = "A simple online contest platform"
homepage = "https://bwinf.de/jugendwettbewerb"
repository = "https://git.bwinf.de/zgtm/medal-prototype"
readme = "README.md"
license = "LGPL-3.0"
#maintenance = { status = "active-developed" }
[features] [features]
watch = ["handlebars-iron/watch"] default = ["rusqlite"]
strict = [] # Treat warnings as a build error complete = ["rusqlite", "postgres", "webbrowser"]
server = ["rusqlite", "postgres"]
desktop = ["rusqlite", "webbrowser"]
watch = ["handlebars-iron/watch"]
strict = [] # Treat warnings as a build error
[dependencies] [dependencies]
rusqlite = "0.14.0"
time = "0.1.40" time = "0.1.40"
iron = "0.5.1" iron = "0.5.1"
rand = "0.5.5" rand = "0.5.5"
mount ="0.3" mount ="0.3"
router = "0.5" router = "0.5"
...@@ -25,19 +34,23 @@ handlebars-iron = "0.25.1" ...@@ -25,19 +34,23 @@ handlebars-iron = "0.25.1"
iron-sessionstorage = "*" iron-sessionstorage = "*"
serde_yaml = "*" serde_yaml = "*"
structopt = "*" structopt = "*"
reqwest = "0.6.2" reqwest = "0.6.2"
linked-hash-map = "0.5.1" linked-hash-map = "0.5.1"
bcrypt = "0.3" bcrypt = "0.3"
webbrowser = "0.5.1"
[dependencies.serde_json] [dependencies.serde_json]
version = "1.0.20" version = "1.0.20"
features = ["preserve_order"] features = ["preserve_order"]
[dependencies.webbrowser]
version = "0.5.1"
optional = true
[dependencies.rusqlite]
version = "0.14.0"
optional = true
[dependencies.postgres] [dependencies.postgres]
version = "0.15" version = "0.15"
features = ["with-time"] features = ["with-time"]
\ No newline at end of file optional = true
\ No newline at end of file
debug: debug:
RUST_BACKTRACE=1 cargo run --features watch -- -a RUST_BACKTRACE=1 cargo run --features 'watch' -- -a
test: test:
RUST_BACKTRACE=1 cargo test --features watch RUST_BACKTRACE=1 cargo test --features 'watch complete'
release: release:
env OPENSSL_LIB_DIR=/usr/lib/x86_64-linux-gnu/ OPENSSL_INCLUDE_DIR=/usr/local/include OPENSSL_STATIC=yes cargo build --release env OPENSSL_LIB_DIR=/usr/lib/x86_64-linux-gnu/ OPENSSL_INCLUDE_DIR=/usr/local/include OPENSSL_STATIC=yes cargo build --release --features 'server'
format: format:
cargo +nightly fmt cargo +nightly fmt
......
use oauth_provider;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct Config {
pub host: Option<String>,
pub port: Option<u16>,
pub self_url: Option<String>,
pub oauth_providers: Option<Vec<oauth_provider::OauthProvider>>,
pub database_file: Option<PathBuf>,
pub database_url: Option<String>,
pub template: Option<String>,
pub no_contest_scan: Option<bool>,
pub open_browser: Option<bool>,
}
#[derive(StructOpt, Debug)]
#[structopt()]
pub struct Opt {
/// Config file to use (default: 'config.json')
#[structopt(short = "c", long = "config", default_value = "config.json", parse(from_os_str))]
pub configfile: PathBuf,
/// Database file to use (default: from config file or 'medal.db')
#[structopt(short = "d", long = "database", parse(from_os_str))]
pub databasefile: Option<PathBuf>,
/// Database file to use (default: from config file or 'medal.db')
#[structopt(short = "D", long = "databaseurl")]
pub databaseurl: Option<String>,
/// Port to listen on (default: from config file or 8080)
#[structopt(short = "p", long = "port")]
pub port: Option<u16>,
/// Reset password of admin user (user_id=1)
#[structopt(short = "a", long = "reset-admin-pw")]
pub resetadminpw: bool,
/// Run medal without scanning for contests
#[structopt(short = "S", long = "no-contest-scan")]
pub nocontestscan: bool,
/// Scan for contests without starting medal
#[structopt(short = "s", long = "only-contest-scan")]
pub onlycontestscan: bool,
/// Automatically open medal in the default browser
#[structopt(short = "b", long = "browser")]
pub openbrowser: bool,
}
pub fn read_config_from_file(file: &Path) -> Config {
use std::io::Read;
println!("Reading configuration file '{}'", file.to_str().unwrap_or("<Encoding error>"));
let mut config: Config = if let Ok(mut file) = std::fs::File::open(file) {
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
serde_json::from_str(&contents).unwrap()
} else {
println!("Configuration file '{}' not found.", file.to_str().unwrap_or("<Encoding error>"));
Default::default()
};
if let Some(ref oap) = config.oauth_providers {
println!("OAuth providers:");
for oap in oap {
println!(" * {}", oap.provider_id);
}
}
if config.host.is_none() {
config.host = Some("[::]".to_string())
}
if config.port.is_none() {
config.port = Some(8080)
}
if config.self_url.is_none() {
config.self_url = Some("http://localhost:8080".to_string())
}
if config.template.is_none() {
config.template = Some("default".to_string())
}
if config.no_contest_scan.is_none() {
config.no_contest_scan = Some(false)
}
if config.open_browser.is_none() {
config.open_browser = Some(false)
}
println!("OAuth providers will be told to redirect to {}", config.self_url.as_ref().unwrap());
config
}
#![cfg(feature = "postgres")]
extern crate bcrypt; extern crate bcrypt;
extern crate postgres; extern crate postgres;
......
#![cfg(feature = "rusqlite")]
extern crate bcrypt; extern crate bcrypt;
extern crate rusqlite; extern crate rusqlite;
......
...@@ -12,9 +12,11 @@ extern crate iron_sessionstorage; ...@@ -12,9 +12,11 @@ extern crate iron_sessionstorage;
extern crate mount; extern crate mount;
extern crate params; extern crate params;
extern crate persistent; extern crate persistent;
#[cfg(feature = "postgres")]
extern crate postgres; extern crate postgres;
extern crate rand; extern crate rand;
extern crate reqwest; extern crate reqwest;
#[cfg(feature = "rusqlite")]
extern crate rusqlite; extern crate rusqlite;
extern crate serde_json; extern crate serde_json;
extern crate serde_yaml; extern crate serde_yaml;
...@@ -22,20 +24,21 @@ extern crate staticfile; ...@@ -22,20 +24,21 @@ extern crate staticfile;
extern crate structopt; extern crate structopt;
extern crate time; extern crate time;
extern crate urlencoded; extern crate urlencoded;
#[cfg(feature = "webbrowser")]
extern crate webbrowser; extern crate webbrowser;
mod db_apply_migrations; mod db_apply_migrations;
mod db_conn; pub mod db_conn;
mod db_conn_postgres; mod db_conn_postgres;
mod db_conn_sqlite; mod db_conn_sqlite;
mod db_objects; mod db_objects;
pub mod config;
pub mod contestreader_yaml; pub mod contestreader_yaml;
pub mod functions; pub mod functions;
pub mod oauth_provider; pub mod oauth_provider;
mod webfw_iron; mod webfw_iron;
pub use db_conn::{MedalConnection, MedalObject}; use db_conn::{MedalConnection, MedalObject};
use functions::SetPassword; // TODO: Refactor, so we don't need to take this from there! use functions::SetPassword; // TODO: Refactor, so we don't need to take this from there!
use db_objects::*; use db_objects::*;
...@@ -45,102 +48,10 @@ use webfw_iron::start_server; ...@@ -45,102 +48,10 @@ use webfw_iron::start_server;
use std::fs; use std::fs;
use std::path; use std::path;
use std::path::{Path, PathBuf}; use std::path::Path;
use structopt::StructOpt; use structopt::StructOpt;
#[derive(Serialize, Deserialize, Clone, Default, Debug)] use config::Config;
pub struct Config {
host: Option<String>,
port: Option<u16>,
self_url: Option<String>,
oauth_providers: Option<Vec<oauth_provider::OauthProvider>>,
database_file: Option<PathBuf>,
database_url: Option<String>,
template: Option<String>,
no_contest_scan: Option<bool>,
open_browser: Option<bool>,
}
fn read_config_from_file(file: &Path) -> Config {
use std::io::Read;
println!("Reading configuration file '{}'", file.to_str().unwrap_or("<Encoding error>"));
let mut config: Config = if let Ok(mut file) = fs::File::open(file) {
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
serde_json::from_str(&contents).unwrap()
} else {
println!("Configuration file '{}' not found.", file.to_str().unwrap_or("<Encoding error>"));
Default::default()
};
if let Some(ref oap) = config.oauth_providers {
println!("OAuth providers:");
for oap in oap {
println!(" * {}", oap.provider_id);
}
}
if config.host.is_none() {
config.host = Some("[::]".to_string())
}
if config.port.is_none() {
config.port = Some(8080)
}
if config.self_url.is_none() {
config.self_url = Some("http://localhost:8080".to_string())
}
if config.template.is_none() {
config.template = Some("default".to_string())
}
if config.no_contest_scan.is_none() {
config.no_contest_scan = Some(false)
}
if config.open_browser.is_none() {
config.open_browser = Some(false)
}
println!("OAuth providers will be told to redirect to {}", config.self_url.as_ref().unwrap());
config
}
#[derive(StructOpt, Debug)]
#[structopt()]
struct Opt {
/// Config file to use (default: 'config.json')
#[structopt(short = "c", long = "config", default_value = "config.json", parse(from_os_str))]
configfile: PathBuf,
/// Database file to use (default: from config file or 'medal.db')
#[structopt(short = "d", long = "database", parse(from_os_str))]
databasefile: Option<PathBuf>,
/// Database file to use (default: from config file or 'medal.db')
#[structopt(short = "D", long = "databaseurl")]
databaseurl: Option<String>,
/// Port to listen on (default: from config file or 8080)
#[structopt(short = "p", long = "port")]
port: Option<u16>,
/// Reset password of admin user (user_id=1)
#[structopt(short = "a", long = "reset-admin-pw")]
resetadminpw: bool,
/// Run medal without scanning for contests
#[structopt(short = "S", long = "no-contest-scan")]
nocontestscan: bool,
/// Scan for contests without starting medal
#[structopt(short = "s", long = "only-contest-scan")]
onlycontestscan: bool,
/// Automatically open medal in the default browser
#[structopt(short = "b", long = "browser")]
openbrowser: bool,
}
fn read_contest(p: &path::PathBuf) -> Option<Contest> { fn read_contest(p: &path::PathBuf) -> Option<Contest> {
use std::fs::File; use std::fs::File;
...@@ -151,8 +62,8 @@ fn read_contest(p: &path::PathBuf) -> Option<Contest> { ...@@ -151,8 +62,8 @@ fn read_contest(p: &path::PathBuf) -> Option<Contest> {
file.read_to_string(&mut contents).unwrap(); file.read_to_string(&mut contents).unwrap();
contestreader_yaml::parse_yaml(&contents, contestreader_yaml::parse_yaml(&contents,
p.file_name().to_owned()?.to_str()?, p.file_name().to_owned()?.to_str()?,
&format!("{}/", p.parent().unwrap().to_str()?)) &format!("{}/", p.parent().unwrap().to_str()?))
} }
fn get_all_contest_info(task_dir: &str) -> Vec<Contest> { fn get_all_contest_info(task_dir: &str) -> Vec<Contest> {
...@@ -245,14 +156,19 @@ fn prepare_and_start_server<C>(mut conn: C, config: Config, onlycontestscan: boo ...@@ -245,14 +156,19 @@ fn prepare_and_start_server<C>(mut conn: C, config: Config, onlycontestscan: boo
if !onlycontestscan { if !onlycontestscan {
add_admin_user(&mut conn, resetadminpw); add_admin_user(&mut conn, resetadminpw);
#[cfg(feature = "webbrowser")]
let self_url = config.self_url.clone(); let self_url = config.self_url.clone();
#[cfg(feature = "webbrowser")]
let open_browser = config.open_browser; let open_browser = config.open_browser;
match start_server(conn, config) { match start_server(conn, config) {
Ok(_) => { Ok(_) => {
println!("Server started"); println!("Server started");
if let (Some(self_url), Some(true)) = (self_url, open_browser) { #[cfg(feature = "webbrowser")]
open_browser_window(&self_url); {
if let (Some(self_url), Some(true)) = (self_url, open_browser) {
open_browser_window(&self_url);
}
} }
} }
Err(_) => println!("Error on server start …"), Err(_) => println!("Error on server start …"),
...@@ -262,6 +178,7 @@ fn prepare_and_start_server<C>(mut conn: C, config: Config, onlycontestscan: boo ...@@ -262,6 +178,7 @@ fn prepare_and_start_server<C>(mut conn: C, config: Config, onlycontestscan: boo
} }
} }
#[cfg(feature = "webbrowser")]
fn open_browser_window(self_url: &str) { fn open_browser_window(self_url: &str) {
match webbrowser::open(&self_url) { match webbrowser::open(&self_url) {
Ok(_) => (), Ok(_) => (),
...@@ -270,10 +187,10 @@ fn open_browser_window(self_url: &str) { ...@@ -270,10 +187,10 @@ fn open_browser_window(self_url: &str) {
} }
fn main() { fn main() {
let opt = Opt::from_args(); let opt = config::Opt::from_args();
//println!("{:?}", opt); // Show in different debug level? //println!("{:?}", opt); // Show in different debug level?
let mut config = read_config_from_file(&opt.configfile); let mut config = config::read_config_from_file(&opt.configfile);
if opt.databasefile.is_some() { if opt.databasefile.is_some() {
config.database_file = opt.databasefile; config.database_file = opt.databasefile;
...@@ -294,23 +211,31 @@ fn main() { ...@@ -294,23 +211,31 @@ fn main() {
config.open_browser = Some(true) config.open_browser = Some(true)
} }
#[cfg(feature = "postgres")]
if config.database_url.is_some() { {
let url = config.database_url.clone().unwrap(); if let Some(url) = config.database_url.clone() {
print!("Using database {} … ", &url);
print!("Using database {} … ", &url); let conn = postgres::Connection::connect(url, postgres::TlsMode::None).unwrap();
let conn = postgres::Connection::connect(url, postgres::TlsMode::None).unwrap(); println!("Connected");
println!("Connected");
prepare_and_start_server(conn, config, opt.onlycontestscan, opt.resetadminpw);
prepare_and_start_server(conn, config, opt.onlycontestscan, opt.resetadminpw); return;
} else { }
let path = config.database_file.clone().unwrap();
print!("Using database file {} … ", &path.to_str().unwrap_or("<unprintable filename>"));
let conn = rusqlite::Connection::open(path).unwrap();
println!("Connected");
prepare_and_start_server(conn, config, opt.onlycontestscan, opt.resetadminpw);
} }
#[cfg(feature = "rusqlite")]
{
if let Some(path) = config.database_file.clone() {
print!("Using database file {} … ", &path.to_str().unwrap_or("<unprintable filename>"));
let conn = rusqlite::Connection::open(path).unwrap();
println!("Connected");
prepare_and_start_server(conn, config, opt.onlycontestscan, opt.resetadminpw);
return;
}
}
println!("No database configured. Try enableing the 'rusqlite' feature during compilation.\nLeaving now.");
} }
#[cfg(test)] #[cfg(test)]
...@@ -338,7 +263,7 @@ mod tests { ...@@ -338,7 +263,7 @@ mod tests {
} }
} }
let mut config = read_config_from_file(Path::new("thisfileshoudnotexist")); let mut config = config::read_config_from_file(Path::new("thisfileshoudnotexist"));
config.port = Some(port); config.port = Some(port);
let srvr = start_server(conn, config); let srvr = start_server(conn, config);
......
...@@ -29,6 +29,7 @@ use db_conn::MedalConnection; ...@@ -29,6 +29,7 @@ use db_conn::MedalConnection;
pub use serde_json::value as json_val; pub use serde_json::value as json_val;
use config::Config;
use iron::typemap::Key; use iron::typemap::Key;
static TASK_DIR: &'static str = "tasks"; static TASK_DIR: &'static str = "tasks";
...@@ -479,7 +480,7 @@ fn submission_post<C>(req: &mut Request) -> IronResult<Response> ...@@ -479,7 +480,7 @@ fn submission_post<C>(req: &mut Request) -> IronResult<Response>
print!("New submission for task {} (graded {}): ", task_id, grade); print!("New submission for task {} (graded {}): ", task_id, grade);
println!("{}", data); println!("{}", data);
*/ */
let result = let result =
with_conn![functions::save_submission, C, req, task_id, &session_token, &csrf_token, data, grade, subtask]; with_conn![functions::save_submission, C, req, task_id, &session_token, &csrf_token, data, grade, subtask];
...@@ -805,7 +806,7 @@ impl<C> Key for SharedDatabaseConnection<C> where C: MedalConnection + 'static ...@@ -805,7 +806,7 @@ impl<C> Key for SharedDatabaseConnection<C> where C: MedalConnection + 'static
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct SharedConfiguration; pub struct SharedConfiguration;
impl Key for SharedConfiguration { impl Key for SharedConfiguration {
type Value = ::Config; type Value = Config;
} }
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
...@@ -856,7 +857,7 @@ fn cookie_warning(req: &mut Request) -> IronResult<Response> { ...@@ -856,7 +857,7 @@ fn cookie_warning(req: &mut Request) -> IronResult<Response> {
} }
} }
pub fn start_server<C>(conn: C, config: ::Config) -> iron::error::HttpResult<iron::Listening> pub fn start_server<C>(conn: C, config: Config) -> iron::error::HttpResult<iron::Listening>
where C: MedalConnection + std::marker::Send + 'static { where C: MedalConnection + std::marker::Send + 'static {
let router = router!( let router = router!(
greet: get "/" => greet_personal::<C>, greet: get "/" => greet_personal::<C>,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment