about summary refs log tree commit diff stats
path: root/pkgs/by-name/ba/back/src/web/mod.rs
blob: cc087ab55b4836494934a7ae791adc8414f76067 (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
use bytes::Bytes;
use http_body_util::combinators::BoxBody;
use hyper::{server::conn::http1, service::service_fn, Method, Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use log::{error, info};
use responses::{html_response, html_response_status, html_response_status_content_type};
use tokio::net::TcpListener;

use std::{convert::Infallible, net::SocketAddr, path::PathBuf, sync::Arc};

use crate::{config::BackConfig, error, git_bug::issue::Status};

mod generate;
mod responses;

async fn match_uri(
    config: Arc<BackConfig>,
    req: Request<hyper::body::Incoming>,
) -> Result<Response<BoxBody<Bytes, Infallible>>, hyper::Error> {
    if req.method() != Method::GET {
        return Ok(html_response_status(
            "Only get requests are supported",
            StatusCode::NOT_ACCEPTABLE,
        ));
    }

    let output = || -> Result<Response<BoxBody<Bytes, Infallible>>, error::Error> {
        match req.uri().path().trim_end_matches("/") {
            "" => Ok(html_response(generate::repos(&config)?)),

            "/style.css" => Ok(responses::html_response_status_content_type(
                include_str!("../../assets/style.css"),
                StatusCode::OK,
                "text/css",
            )),

            path if path.ends_with("/issues/open") => {
                let repo_path = PathBuf::from(
                    path.strip_suffix("/issues/open")
                        .expect("This suffix exists")
                        .strip_prefix("/")
                        .expect("This also exists"),
                );

                let issues = generate::issues(&config, Status::Open, Status::Closed, &repo_path)?;
                Ok(html_response(issues))
            }
            path if path.ends_with("/issues/closed") => {
                let repo_path = PathBuf::from(
                    path.strip_suffix("/issues/closed")
                        .expect("This suffix exists")
                        .strip_prefix("/")
                        .expect("This also exists"),
                );

                let issues = generate::issues(&config, Status::Closed, Status::Open, &repo_path)?;
                Ok(html_response(issues))
            }
            path if path.ends_with("/issues/feed") => {
                let repo_path = PathBuf::from(
                    path.strip_suffix("/issues/feed")
                        .expect("This suffix exists")
                        .strip_prefix("/")
                        .expect("This also exists"),
                );

                let feed = generate::feed(&config, &repo_path)?;
                Ok(html_response_status_content_type(
                    feed,
                    StatusCode::OK,
                    "text/xml",
                ))
            }

            path if path.contains("/issue/") => {
                let (repo_path, prefix) = {
                    let split: Vec<&str> = path.split("/issue/").collect();

                    let prefix =
                        gix::hash::Prefix::from_hex(split[1]).map_err(error::Error::from)?;

                    let repo_path =
                        PathBuf::from(split[0].strip_prefix("/").expect("This prefix exists"));

                    (repo_path, prefix)
                };
                Ok(html_response(generate::issue(&config, &repo_path, prefix)?))
            }

            other => Ok(responses::html_response_status_content_type(
                format!("'{}' not found", other),
                StatusCode::NOT_FOUND,
                "text/plain",
            )),
        }
    };
    match output() {
        Ok(response) => Ok(response),
        Err(err) => Ok(err.into_response()),
    }
}

pub async fn main(config: Arc<BackConfig>) -> Result<(), error::Error> {
    let addr: SocketAddr = ([127, 0, 0, 1], 8000).into();

    let listener = TcpListener::bind(addr)
        .await
        .map_err(|err| error::Error::TcpBind { addr, err })?;
    info!("Listening on http://{}", addr);
    loop {
        let (stream, _) = listener
            .accept()
            .await
            .map_err(|err| error::Error::TcpAccept { err })?;
        let io = TokioIo::new(stream);

        let local_config = Arc::clone(&config);

        let service = service_fn(move |req| match_uri(Arc::clone(&local_config), req));

        tokio::task::spawn(async move {
            if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
                error!("Error serving connection: {:?}", err);
            }
        });
    }
}