1
mod dataset;
2
mod fetch;
3
use self::{
4
    dataset::{load_datasets, Dataset, DatasetVersion},
5
    fetch::fetch_all,
6
};
7
use crate::{server_config::ServerConfig, server_error::MyError};
8
use axum::{
9
    extract::{Path, State},
10
    response::Html,
11
    routing::get,
12
    Router,
13
};
14
use chrono::{DateTime, Utc};
15
use serde::Serialize;
16
use std::{
17
    error::Error,
18
    path::PathBuf,
19
    sync::{Arc, RwLock},
20
};
21
use tera::{Context, Tera};
22

            
23
#[derive(Serialize)]
24
struct DatasetSummary<'a> {
25
    id: &'a str,
26
    latest_version: Option<DateTime<Utc>>,
27
    number_of_versions: usize,
28
}
29

            
30
impl<'a> From<&'a Dataset> for DatasetSummary<'a> {
31
    fn from(dataset: &'a Dataset) -> Self {
32
        DatasetSummary {
33
            id: &dataset.id,
34
            latest_version: dataset.latest_version(),
35
            number_of_versions: dataset.versions.len(),
36
        }
37
    }
38
}
39

            
40
1
async fn route_get_index(State(state): State<SharedState>) -> Result<Html<String>, MyError> {
41
1
    let state = &mut state.read().unwrap();
42
1
    let mut context = Context::new();
43
1
    let datasets: Vec<_> = state.datasets.iter().map(DatasetSummary::from).collect();
44
1
    context.insert("datasets", &datasets);
45
1
    let html = state.tera.render("index.html", &context)?;
46
1
    Ok(Html(html))
47
1
}
48

            
49
#[derive(Serialize)]
50
struct DatasetVersionSummary {
51
    date: DateTime<Utc>,
52
    number_of_quads: usize,
53
}
54

            
55
impl<'a> From<&'a DatasetVersion> for DatasetVersionSummary {
56
    fn from(version: &'a DatasetVersion) -> Self {
57
        DatasetVersionSummary {
58
            date: version.date,
59
            number_of_quads: version.number_of_quads(),
60
        }
61
    }
62
}
63

            
64
fn get_dataset<'a>(datasets: &'a [Dataset], id: &str) -> Result<&'a Dataset, MyError> {
65
    if let Some(dataset) = datasets.iter().find(|d| d.id == id) {
66
        Ok(dataset)
67
    } else {
68
        Err(MyError::NotFound(format!("No dataset with id {}", id)))
69
    }
70
}
71

            
72
fn get_dataset_version<'a>(
73
    datasets: &'a [Dataset],
74
    id: &str,
75
    date: DateTime<Utc>,
76
) -> Result<(&'a Dataset, &'a DatasetVersion), MyError> {
77
    let dataset = get_dataset(datasets, id)?;
78
    if let Some(version) = dataset.versions.iter().find(|v| v.date == date) {
79
        Ok((dataset, version))
80
    } else {
81
        Err(MyError::NotFound(format!(
82
            "No dataset with id {} and version {}",
83
            id, date
84
        )))
85
    }
86
}
87

            
88
async fn route_get_dataset(
89
    State(state): State<SharedState>,
90
    Path(id): Path<String>,
91
) -> Result<Html<String>, MyError> {
92
    let state = &mut state.read().unwrap();
93
    let dataset = get_dataset(&state.datasets, &id)?;
94
    let mut context = Context::new();
95
    let summary = DatasetSummary::from(dataset);
96
    context.insert("dataset", &summary);
97
    let versions: Vec<_> = dataset
98
        .versions
99
        .iter()
100
        .map(DatasetVersionSummary::from)
101
        .collect();
102
    context.insert("versions", &versions);
103
    Ok(Html(state.tera.render("dataset.html", &context)?))
104
}
105

            
106
async fn route_get_dataset_version(
107
    State(state): State<SharedState>,
108
    Path((id, version)): Path<(String, DateTime<Utc>)>,
109
) -> Result<Html<String>, MyError> {
110
    let state = &mut state.read().unwrap();
111
    let (dataset, version) = get_dataset_version(&state.datasets, &id, version)?;
112
    let subjects = version.subjects();
113
    let dataset = DatasetSummary::from(dataset);
114
    let version = DatasetVersionSummary::from(version);
115
    let mut context = Context::new();
116
    context.insert("dataset", &dataset);
117
    context.insert("version", &version);
118
    context.insert("subjects", &subjects);
119
    Ok(Html(state.tera.render("version.html", &context)?))
120
}
121

            
122
async fn route_get_dataset_version_subject(
123
    State(state): State<SharedState>,
124
    Path((id, version, subject)): Path<(String, DateTime<Utc>, String)>,
125
) -> Result<Html<String>, MyError> {
126
    let state = &mut state.read().unwrap();
127
    let (dataset, version) = get_dataset_version(&state.datasets, &id, version)?;
128
    let pos = version.pos(&subject);
129
    let dataset = DatasetSummary::from(dataset);
130
    let version = DatasetVersionSummary::from(version);
131
    let mut context = Context::new();
132
    context.insert("dataset", &dataset);
133
    context.insert("version", &version);
134
    context.insert("subject", &subject);
135
    context.insert("pos", &pos);
136
    Ok(Html(state.tera.render("subject.html", &context)?))
137
}
138

            
139
async fn route_get_crawl(State(state): State<SharedState>) -> Result<Html<String>, MyError> {
140
    let dataset_configs: Vec<_>;
141
    {
142
        let state = &mut state.read().unwrap();
143
        dataset_configs = state.config.datasets.clone();
144
    }
145
    fetch_all(dataset_configs, &PathBuf::from("datasets")).await?;
146
    Ok(Html("Ok".into()))
147
}
148

            
149
type SharedState = Arc<RwLock<AppState>>;
150

            
151
struct AppState {
152
    config: ServerConfig,
153
    datasets: Vec<Dataset>,
154
    tera: Tera,
155
}
156

            
157
1
fn init_dataset(config: &ServerConfig) -> Result<(), Box<dyn Error>> {
158
1
    let dir = PathBuf::from("datasets");
159
1
    for c in &config.datasets {
160
        std::fs::create_dir_all(dir.join(&c.name))?;
161
    }
162
1
    Ok(())
163
1
}
164

            
165
pub fn create_app(
166
    config: ServerConfig,
167
) -> Result<axum::routing::IntoMakeService<Router>, Box<dyn Error>> {
168
1
    init_dataset(&config)?;
169
1
    let tera = Tera::new("templates/**/[a-zA-Z0-9]*")?;
170
1
    let datasets = load_datasets("datasets")?;
171
1
    let shared_state = Arc::new(RwLock::new(AppState {
172
1
        datasets,
173
1
        tera,
174
1
        config,
175
1
    }));
176
1
    Ok(Router::new()
177
1
        .route("/", get(route_get_index))
178
1
        .route("/dataset/", get(route_get_index))
179
1
        .route("/dataset/:id", get(route_get_dataset))
180
1
        .route("/dataset/:id/:version", get(route_get_dataset_version))
181
1
        .route(
182
1
            "/dataset/:id/:version/:subject",
183
1
            get(route_get_dataset_version_subject),
184
1
        )
185
1
        .route("/crawl", get(route_get_crawl))
186
1
        .with_state(Arc::clone(&shared_state))
187
1
        .into_make_service())
188
1
}
189

            
190
#[cfg(test)]
191
mod test {
192
    use axum_test::TestServer;
193

            
194
    use crate::server_config::ServerConfig;
195

            
196
1
    #[tokio::test]
197
1
    async fn create_server() {
198
1
        let my_app = super::create_app(ServerConfig { datasets: vec![] }).unwrap();
199
1
        let server = TestServer::new(my_app).unwrap();
200
1
        let response = server.get("/").await;
201
1
        assert_eq!(response.status_code(), 200);
202
    }
203
}