about summary refs log tree commit diff
path: root/src/main.rs
blob: 4036d46b230ea4e6d14fb1c8e15ff0e071f2a275 (plain) (blame)
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use log::{debug, error, info};
use std::env;
use http_types::Url;
use warp::{Filter, host::Authority, path::FullPath};

#[tokio::main]
async fn main() -> Result<(), kittybox::database::StorageError> {
    // TODO json logging in the future?
    let logger_env = env_logger::Env::new().filter_or("RUST_LOG", "info");
    env_logger::init_from_env(logger_env);

    info!("Starting the kittybox server...");

    let backend_uri: String;
    match env::var("BACKEND_URI") {
        Ok(val) => {
            debug!("Backend URI: {}", val);
            backend_uri = val
        }
        Err(_) => {
            error!("BACKEND_URI is not set, cannot find a database");
            std::process::exit(1);
        }
    };
    let token_endpoint: Url;
    match env::var("TOKEN_ENDPOINT") {
        Ok(val) => {
            debug!("Token endpoint: {}", val);
            match Url::parse(&val) {
                Ok(val) => token_endpoint = val,
                _ => {
                    error!("Token endpoint URL cannot be parsed, aborting.");
                    std::process::exit(1)
                }
            }
        }
        Err(_) => {
            error!("TOKEN_ENDPOINT is not set, will not be able to authorize users!");
            std::process::exit(1)
        }
    }
    let authorization_endpoint: Url;
    match env::var("AUTHORIZATION_ENDPOINT") {
        Ok(val) => {
            debug!("Auth endpoint: {}", val);
            match Url::parse(&val) {
                Ok(val) => authorization_endpoint = val,
                _ => {
                    error!("Authorization endpoint URL cannot be parsed, aborting.");
                    std::process::exit(1)
                }
            }
        }
        Err(_) => {
            error!("AUTHORIZATION_ENDPOINT is not set, will not be able to confirm token and ID requests using IndieAuth!");
            std::process::exit(1)
        }
    }

    let media_endpoint: Option<String> = env::var("MEDIA_ENDPOINT").ok();

    let internal_token: Option<String> = env::var("KITTYBOX_INTERNAL_TOKEN").ok();

    let cookie_secret: String = match env::var("COOKIE_SECRET").ok() {
        Some(value) => value,
        None => {
            if let Ok(filename) = env::var("COOKIE_SECRET_FILE") {
                /*use async_std::io::ReadExt;

                let mut file = async_std::fs::File::open(filename).await?;
                let mut temp_string = String::new();
                file.read_to_string(&mut temp_string).await?;

                temp_string*/
                todo!()
            } else {
                error!("COOKIE_SECRET or COOKIE_SECRET_FILE is not set, will not be able to log in users securely!");
                std::process::exit(1);
            }
        }
    };

    let host: std::net::SocketAddr = match env::var("SERVE_AT")
        .ok()
        .unwrap_or_else(|| "0.0.0.0:8080".to_string())
        .parse() {
            Ok(addr) => addr,
            Err(e) => {
                error!("Cannot parse SERVE_AT: {}", e);
                std::process::exit(1);
            }
        };

    if backend_uri.starts_with("redis") {
        println!("The Redis backend is deprecated.");
        std::process::exit(1);
    } else if backend_uri.starts_with("file") {
        
        let database = {
            let folder = backend_uri.strip_prefix("file://").unwrap();
            let path = std::path::PathBuf::from(folder);
            kittybox::database::FileStorage::new(path).await?
        };

        // TODO interpret HEAD
        let homepage = kittybox::util::require_host()
            .and(warp::get())
            .and(warp::path::end())
            // TODO fetch content from the database
            // TODO parse content-type and determine appropriate response
            .map(|host| format!("front page for {}!", host));
        
        let micropub = warp::path("micropub")
            .and(warp::path::end()
                 .and(warp::get()
                      .and(kittybox::micropub::query(database))
                      .or(warp::post()
                          .and(kittybox::util::require_host())
                          .map(|host| "micropub post!"))
                      .or(warp::options()
                          .map(|| warp::reply::json::<Option<()>>(&None))
                          // TODO: why doesn't this work?
                          // .map(warp::reply::with::header("Allow", "GET, POST"))
                          .map(|reply| warp::reply::with_header(reply, "Allow", "GET, POST"))
                      ))
                 .or(warp::get()
                     .and(warp::path("client"))
                     .and(warp::path::end())
                     .map(|| kittybox::MICROPUB_CLIENT)));

        let media = warp::path("media")
            .and(warp::path::end()
                 .and(kittybox::util::require_host())
                 .map(|host| "media endpoint?...")
                 .or(kittybox::util::require_host()
                     .and(warp::path::param())
                     .map(|host: Authority, path: String| format!("media file {}", path))));
        
        // TODO remember how login logic works because I forgor
        let login = warp::path("login")
            .and(warp::path("callback")
                 .map(|| "callback!")
                 // TODO form on GET and handler on POST
                 .or(warp::path::end().map(|| "login page!")));

        // TODO prettier error response
        let coffee = warp::path("coffee")
            .map(|| warp::reply::with_status("I'm a teapot!", warp::http::StatusCode::IM_A_TEAPOT));
        
        // TODO interpret HEAD
        let static_files = warp::get()
            .and(warp::path!("static" / String))
            .map(|path| path);

        // TODO interpret HEAD
        let catchall = warp::get()
            .and(kittybox::util::require_host())
            .and(warp::path::full())
            .map(|host: Authority, path: FullPath| host.to_string() + path.as_str() + ".json")
            // TODO fetch content from the database
            // TODO parse content-type and determine appropriate response
            ;

        let health = warp::path("health").and(warp::path::end()).map(|| "OK");
        // TODO instrumentation middleware (see metrics.rs for comments)
        //let metrics = warp::path("metrics").and(warp::path::end()).map(kittybox::metrics::gather);
        let app = homepage
            .or(login)
            .or(static_files)
            .or(coffee)
            .or(health)
            .or(micropub)
            .or(media)
            .or(catchall)
            ;

        let server = warp::serve(app);

        // TODO use warp::Server::bind_with_graceful_shutdown
        info!("Listening on {:?}", host);
        server.bind(host).await;
        Ok(())
    } else {
        println!("Unknown backend, not starting.");
        std::process::exit(1);
    }
}