medal/
main.rs

1/*  medal                                                                                                            *\
2 *  Copyright (C) 2022  Bundesweite Informatikwettbewerbe, Robert Czechowski                                                            *
3 *                                                                                                                   *
4 *  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero        *
5 *  General Public License as published  by the Free Software Foundation, either version 3 of the License, or (at    *
6 *  your option) any later version.                                                                                  *
7 *                                                                                                                   *
8 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the       *
9 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public      *
10 *  License for more details.                                                                                        *
11 *                                                                                                                   *
12 *  You should have received a copy of the GNU Affero General Public License along with this program.  If not, see   *
13\*  <http://www.gnu.org/licenses/>.                                                                                  */
14
15#![cfg_attr(feature = "strict", deny(warnings))]
16
17#[macro_use]
18extern crate iron;
19#[macro_use]
20extern crate router;
21#[macro_use]
22extern crate serde_derive;
23
24extern crate csv;
25extern crate handlebars_iron;
26extern crate iron_sessionstorage;
27extern crate mount;
28extern crate params;
29extern crate percent_encoding;
30extern crate persistent;
31extern crate rand;
32extern crate reqwest;
33extern crate serde_json;
34extern crate serde_yaml;
35extern crate sha2;
36extern crate staticfile;
37extern crate structopt;
38extern crate time;
39extern crate urlencoded;
40extern crate webauthn_rs;
41
42#[cfg(feature = "postgres")]
43extern crate postgres;
44#[cfg(feature = "rusqlite")]
45extern crate rusqlite;
46#[cfg(feature = "webbrowser")]
47extern crate webbrowser;
48
49pub mod config;
50pub mod contestreader_yaml;
51pub mod core;
52pub mod db_conn;
53pub mod helpers;
54
55mod db_apply_migrations;
56mod db_conn_postgres;
57mod db_conn_sqlite_new;
58mod db_objects;
59mod webfw_iron;
60
61use db_conn::{MedalConnection, MedalObject};
62use helpers::SetPassword;
63use webfw_iron::start_server;
64
65use config::Config;
66
67fn refresh_all_contests<C>(conn: &mut C, config: &Config)
68    where C: MedalConnection,
69          db_objects::Contest: db_conn::MedalObject<C>
70{
71    conn.reset_all_contest_visibilities();
72    conn.reset_all_taskgroup_visibilities();
73
74    print!("Scanning for contests … ");
75    let v = contestreader_yaml::get_all_contest_info("tasks/", config);
76    println!(" Done");
77
78    print!("Storing contests information … ");
79    for mut contest_info in v {
80        contest_info.save(conn);
81        {
82            use std::io::Write;
83            print!(".");
84            std::io::stdout().flush().unwrap();
85        }
86    }
87    println!(" Done");
88}
89
90fn add_admin_user<C>(conn: &mut C, resetpw: bool)
91    where C: MedalConnection {
92    let mut admin = match conn.get_user_by_id(1) {
93        None => {
94            print!("New Database. Creating new admin user with credentials 'admin':");
95            conn.new_session("")
96        }
97        Some(user) => {
98            if !resetpw {
99                return;
100            }
101            print!("Request to reset admin password. Set credentials 'admin':");
102            user
103        }
104    };
105
106    let password = helpers::make_unambiguous_lowercase_code(10);
107    print!("'{}', ", &password);
108
109    let logincode = helpers::make_admincode();
110    print!(" logincode:'{}' …", &logincode);
111
112    admin.is_admin = Some(true);
113    admin.username = Some("admin".into());
114    admin.logincode = Some(logincode);
115    match admin.set_password(&password) {
116        None => println!(" FAILED! (Password hashing error)"),
117        _ => {
118            conn.save_session(admin);
119            println!(" Done");
120        }
121    }
122}
123
124fn prepare_and_start_server<C>(mut conn: C, config: Config)
125    where C: MedalConnection + std::marker::Send + 'static,
126          db_objects::Contest: db_conn::MedalObject<C>
127{
128    db_apply_migrations::test(&mut conn);
129
130    if config.only_contest_scan == Some(true) || config.no_contest_scan != Some(true) {
131        refresh_all_contests(&mut conn, &config);
132    }
133
134    if config.only_contest_scan != Some(true) {
135        add_admin_user(&mut conn, config.reset_admin_pw.unwrap_or(false));
136
137        #[cfg(feature = "webbrowser")]
138        let self_url = config.self_url.clone();
139        #[cfg(feature = "webbrowser")]
140        let open_browser = config.open_browser;
141
142        match start_server(conn, config) {
143            Ok(_) => {
144                println!("Server started");
145
146                #[cfg(feature = "webbrowser")]
147                {
148                    if let (Some(self_url), Some(true)) = (self_url, open_browser) {
149                        open_browser_window(&self_url);
150                    }
151                }
152            }
153            Err(_) => println!("Error on server start …"),
154        };
155
156        println!("Could not run server. Is the port already in use?");
157    }
158}
159
160#[cfg(feature = "webbrowser")]
161fn open_browser_window(self_url: &str) {
162    match webbrowser::open(&self_url) {
163        Ok(_) => (),
164        Err(e) => println!("Error while opening webbrowser: {:?}", e),
165    }
166}
167
168fn main() {
169    let config = config::get_config();
170
171    if config.version == Some(true) {
172        println!("Medal version {}", &env!("CARGO_PKG_VERSION"));
173        return;
174    }
175
176    #[cfg(feature = "debug")]
177    println!("Using config: {:#?}", config);
178
179    #[cfg(feature = "postgres")]
180    {
181        if let Some(url) = config.database_url.clone() {
182            #[cfg(feature = "debug")]
183            print!("Using database {} … ", &url);
184            #[cfg(not(feature = "debug"))]
185            {
186                let (begin_middle, end) = url.split_at(url.find('@').unwrap_or(0));
187                let (begin, _middle) = begin_middle.split_at(begin_middle.rfind(':').unwrap_or(0));
188                print!("Using database {}:***{} … ", begin, end);
189            }
190            let conn = postgres::Connection::connect(url, postgres::TlsMode::None).unwrap();
191            println!("Connected");
192
193            prepare_and_start_server(conn, config);
194            return;
195        }
196    }
197
198    #[cfg(feature = "rusqlite")]
199    {
200        if let Some(path) = config.database_file.clone() {
201            print!("Using database file {} … ", &path.to_str().unwrap_or("<unprintable filename>"));
202            let conn = rusqlite::Connection::open(path).unwrap();
203            println!("Connected");
204
205            conn.execute("PRAGMA foreign_keys = ON;", &[]).unwrap();
206            let foreign_keys: bool = conn.query_row("PRAGMA foreign_keys", &[], |row| row.get(0)).unwrap();
207            if !foreign_keys {
208                println!("Sqlite FOREIGN KEY support could NOT be enabled! Stopping now …");
209                return;
210            }
211            println!("Sqlite FOREIGN KEY support enabled");
212
213            prepare_and_start_server(conn, config);
214            return;
215        }
216    }
217
218    println!("No database configured. Try enableing the 'rusqlite' feature during compilation.\nLeaving now.");
219}
220
221#[cfg(test)]
222mod tests;