main.rs 7.94 KB
Newer Older
1
2
#![feature(try_trait)]

Robert Czechowski's avatar
Robert Czechowski committed
3
4
5
6
7
8
9
#[macro_use]
extern crate iron;
#[macro_use]
extern crate router;
#[macro_use]
extern crate serde_derive;

10
11
12
#[macro_use]
extern crate structopt;

13
extern crate rusqlite;
Robert Czechowski's avatar
Robert Czechowski committed
14
15
16
17
18
19
20
21
22
extern crate iron_sessionstorage;
extern crate urlencoded;
extern crate time;
extern crate persistent;
extern crate rand;
extern crate mount;
extern crate staticfile;
extern crate handlebars_iron;
extern crate serde_json;
23
24
25
extern crate params;
extern crate reqwest;
extern crate serde_yaml;
26
27
28
29
30
31
32
33

use rusqlite::Connection;

mod db_apply_migrations;
mod db_conn_sqlite;
mod db_conn;
mod db_objects;

34
use functions::SetPassword; // TODO: Refactor, so we don't need to take this from there!
35
36
37
38
use db_conn::{MedalConnection, MedalObject};

use db_objects::*;

Robert Czechowski's avatar
Robert Czechowski committed
39
mod webfw_iron;
40
mod configreader_yaml;
Robert Czechowski's avatar
Robert Czechowski committed
41
42
43
44
45
46
47
48

use webfw_iron::start_server;

mod functions;

use std::path;
use std::fs;

49
50
51
use std::path::{Path,PathBuf};
use structopt::StructOpt;

52
#[derive(Serialize, Deserialize, Clone, Default)]
53
pub struct Config {
54
55
56
57
    host: Option<String>,
    port: Option<u16>,
    self_url: Option<String>,
    oauth_url: Option<String>,
58
59
60
61
    oauth_client_id: Option<String>,
    oauth_client_secret: Option<String>,
    oauth_access_token_url: Option<String>,
    oauth_user_data_url: Option<String>,
62
    database_file: Option<PathBuf>,
63
64
}

65
fn read_config_from_file(file: &Path) -> Config {
66
    use std::io::Read;
67
68

    println!("Reading Config file '{}'", file.to_str().unwrap_or("<Encoding error>"));
69

70
    let mut config : Config = if let Ok(mut file) = fs::File::open(file) {
71
72
73
74
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        serde_json::from_str(&contents).unwrap()
    } else {
75
        println!("Configuration file '{}' not found.", file.to_str().unwrap_or("<Encoding error>"));
76
        Default::default()
77
78
79
80
    };

    if config.host.is_none() {config.host = Some("[::]".to_string())}
    if config.port.is_none() {config.port = Some(8080)}
81
    if config.self_url.is_none() {config.self_url = Some("http://localhost:8080".to_string())}
82

83
    println!("I will ask OAuth-providers to redirect to {}", config.self_url.as_ref().unwrap());
84

85
    config
86
}
87

88
89
90
91
92
93
94
95
96
97
#[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>,
98
99
100
101

    /// Port to listen on (default: from config file or 8080)
    #[structopt(short = "p", long = "port")]
    port: Option<u16>,
102
103
104
105

    /// Reset password of admin user (user_id=1)
    #[structopt(short = "a", long = "reset-admin-pw")]
    resetadminpw: bool,
106
107
108
109
}



Robert Czechowski's avatar
Robert Czechowski committed
110
fn read_contest(p: &path::PathBuf) -> Option<Contest> {
111
112
    use std::fs::File;
    use std::io::Read;
Robert Czechowski's avatar
Robert Czechowski committed
113
    println!("Try to read some file …");
114

115
116
117
    let mut file = File::open(p).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
118
119

    configreader_yaml::parse_yaml(&contents, p.file_name().to_owned()?.to_str()?, &format!("{}/", p.parent().unwrap().to_str()?))
Robert Czechowski's avatar
Robert Czechowski committed
120
121
122
123
124
125
126
127
128
129
130
}

fn get_all_contest_info(task_dir: &str) -> Vec<Contest> {
    fn walk_me_recursively(p: &path::PathBuf, contests: &mut Vec<Contest>) {
        match fs::read_dir(p) {
            Ok(paths) => for path in paths {
                let p = path.unwrap().path();
                walk_me_recursively(&p, contests);
            },
            _ => (),
        }
131

132
        if p.file_name().unwrap().to_string_lossy().to_string().ends_with(".yaml") {
Robert Czechowski's avatar
Robert Czechowski committed
133
134
135
136
            match read_contest(p) {
                Some(contest) => contests.push(contest),
                _ => (),
            }
137
        };
Robert Czechowski's avatar
Robert Czechowski committed
138
139
    };

140

Robert Czechowski's avatar
Robert Czechowski committed
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
    let mut contests = Vec::new();
    match fs::read_dir(task_dir) {
        Err(why) => println!("Error opening tasks directory! {:?}", why.kind()),
        Ok(paths) => for path in paths {
            walk_me_recursively(&path.unwrap().path(), &mut contests);
        },
    };

    contests
}

fn refresh_all_contests(conn : &mut Connection) {
    let v = get_all_contest_info("tasks/");

    for mut contest_info in v {
        contest_info.save(conn);
    }
}

160
161
162
163
164
165
166
167
168
169
fn add_admin_user(conn: &mut Connection, resetpw: bool) {
    let mut admin = match conn.get_user_by_id(1) {
        None => {
            print!("New Database. Creating new admin user with credentials 'admin':");
            conn.new_session()
            
        },
        Some(user) => {
            if !resetpw {
                return
170
            }
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
            print!("Request to reset admin password. Set credentials 'admin':");
            user
        }
        _ => return
    };

    use rand::{thread_rng, Rng, distributions::Alphanumeric};

    let password: String = thread_rng().sample_iter(&Alphanumeric)
        .filter(|x| {let x = *x; !(x == 'l' || x == 'I' || x == '1' || x == 'O' || x == 'o' || x == '0')})
        .take(8).collect();
    print!("'{}' …", &password);

    admin.username = Some("admin".into());
    match admin.set_password(&password) {
        None => println!("FAILED! (Password hashing error)"),
        _ => {
            conn.save_session(admin);
            println!("Done");
190
        }
191
192
193
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
194
fn main() {
195
196
    let opt = Opt::from_args();
    println!("{:?}", opt);
197

198
199
200
    let mut config = read_config_from_file(&opt.configfile);

    if opt.databasefile.is_some() { config.database_file = opt.databasefile; }
201
    if opt.port.is_some() { config.port = opt.port; }
202

203
    let mut conn = match config.database_file {
204
        Some(ref path) => {println!("Using database file {}", &path.to_str().unwrap_or("<unprintable filename>"));  Connection::create(path)},
205
        None => {println!("Using default database file ./medal.db"); Connection::create(&Path::new("medal.db"))},
206
    };
207

Robert Czechowski's avatar
Robert Czechowski committed
208
209
210
    db_apply_migrations::test(&mut conn);

    refresh_all_contests(&mut conn);
211

212
213
214
    println!("Hello, world!");

    let contest = conn.get_contest_by_id_complete(1);
215
216
217
    
    add_admin_user(&mut conn, opt.resetadminpw);
    
218
219

    println!("Contest {}", contest.name);
220

221
222
223
224
225
226
227
    for taskgroup in contest.taskgroups {
        print!("  Task {}: ", taskgroup.name);
        for task in taskgroup.tasks {
            print!("{} ({}) ", task.stars, task.location);
        }
        println!("");
    }
228

229
    start_server(conn, config);
Robert Czechowski's avatar
Robert Czechowski committed
230

231
    println!("Could not run server. Is the port already in use?");
232
}
233
234
235
236
237
238
239
240
241
242
243



#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn start_server_and_check_request() {
        use std::{thread, time};

244

245
246
247
248
249
250
251
252
253
254
        let mut conn = Connection::open_in_memory().unwrap();
        db_apply_migrations::test(&mut conn);

        // add contests / tasks here

        use std::sync::{Arc, Mutex, Condvar};
        let pair = Arc::new((Mutex::new(false), Condvar::new()));
        let pair_ = pair.clone();

        let mut config = read_config_from_file(Path::new("thisfileshoudnotexist"));
255

256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
        let srvr = start_server(conn, config);

        thread::spawn(move || {
            // wait for server to start:
            thread::sleep(time::Duration::from_millis(100));

            use std::io::Read;

            let mut resp = reqwest::get("http://localhost:8080").unwrap();
            assert!(resp.status().is_success());

            let mut content = String::new();
            resp.read_to_string(&mut content);
            assert!(content.contains("<h1>Jugendwettbewerb Informatik</h1>"));
            assert!(!content.contains("Error"));
271

272
273
274
275
276
277
            let &(ref lock, ref cvar) = &*pair_;
            let mut should_exit = lock.lock().unwrap();
            *should_exit = true;
            cvar.notify_one();
            //fs::copy("foo.txt", "bar.txt").unwrap();
        });
278

279
280
281
282
283
284
285
286
        // Copied from docs
        let &(ref lock, ref cvar) = &*pair;
        let mut should_exit = lock.lock().unwrap();
        while !*should_exit {
            should_exit = cvar.wait(should_exit).unwrap();
        }

        srvr.unwrap().close().unwrap();
287

288
289
290
        assert!(true);
    }
}