aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.rs
blob: 93397faef60d4e06c67219a4459836d47b5ca920 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! # Services
//!
//! All of these endpoints will be served with the URL prefix of a given network.
//! For example, if URL prefix is `testnet`, `/register` is found at `/testnet/register`.
//!
//! ## /register
//! - Student creates their own 2048 bit RSA `keypair`
//! - Downloads `Gradecoin`'s Public Key from Moodle
//! - Encrypts their JSON wrapped `Public Key` and `Student ID` using Gradecoin's Public Key
//! - Their public key is now in our Db under [`block::User::public_key`] and can be used to sign their JWT's during requests
//!
//! ## /transaction
//! - offer a [`block::Transaction`] - POST request
//!     - The request should have `Authorization`
//!     - The request header should be signed by the Public Key of the `by` field in the transaction
//! - fetch the list of `Transaction`s - GET request
//!
//! ## /block
//! - offer a [`block::Block`] - POST request
//!     - The request should have `Authorization`
//!     - The [`block::Block::transaction_list`] of the block should be a subset of [`block::Db::pending_transactions`]
//! - fetch the last accepted [`block::Block`] - GET request
//!
//! `Authorization`: The request header should have Bearer JWT.Token signed with Student Public Key
//!
//! ## /config
//! - Get the current [`config::Config`] as JSON - GET request
//!
//! # Configuration
//!
//! The default configuration file if `config.yaml`, which will run if no command line arguments are given.
//!
//! You can give one or more configuration files as command line arguments.
//! This will run all of them at the same time.
//! Make sure that the names and URL prefixes don't clash, otherwise it will lead to undefined behavior.
//!
//! Example:
//! ```sh
//! # Run both the main network (at /) and testnet (at /testnet)
//! # For example, register for main network at `localhost:8080/register`,
//! # testnet network at `localhost:8080/testnet/register`
//! $ cargo run config.yaml testnet.yaml
//! ```
//!
//! See [`config::Config`] struct for more information about the configurable fields.
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::unused_async)]

mod block;
mod config;
mod custom_filters;
mod db;
mod handlers;
mod routes;
mod student;

use crate::config::Config;
pub use block::{Fingerprint, Id};
use db::Db;
use lazy_static::lazy_static;
use log::error;
use std::fs;
use warp::Filter;

#[tokio::main]
async fn main() {
    log4rs::init_file("log.conf.yml", log4rs::config::Deserializers::default()).unwrap();

    let mut args: Vec<String> = std::env::args().collect();

    if args.len() == 1 {
        // config.yaml is the default configuration file
        args.push("config.yaml".to_string());
    }

    let combined_routes = args
        .into_iter()
        .skip(1) // Skip the program name
        .filter_map(|filename| {
            Config::read(&filename).map(|config| routes::network(Db::new(config)))
        })
        .reduce(|routes, route| routes.or(route).unify().boxed());

    let routes = match combined_routes {
        Some(r) => r,
        None => {
            // Exit the program if there's no successfully loaded config file.
            error!("Failed to load any config files!");
            return;
        }
    };

    // gradecoin-site (zola) outputs a public/, we serve it here
    let static_route = warp::any().and(warp::fs::dir("public"));

    let api = routes.or(static_route);

    // Start the server
    let point = ([127, 0, 0, 1], 8080);
    warp::serve(api).run(point).await;
}

lazy_static! {
    static ref PRIVATE_KEY: String =
        fs::read_to_string("secrets/gradecoin.pem").expect("error reading 'secrets/gradecoin.pem'");
}