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
7
#[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
7
    fn from(dataset: &'a Dataset) -> Self {
32
7
        DatasetSummary {
33
7
            id: &dataset.id,
34
7
            latest_version: dataset.latest_version(),
35
7
            number_of_versions: dataset.versions.len(),
36
7
        }
37
7
    }
38
}
39

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

            
49
3
#[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
3
    fn from(version: &'a DatasetVersion) -> Self {
57
3
        DatasetVersionSummary {
58
3
            date: version.date,
59
3
            number_of_quads: version.number_of_quads(),
60
3
        }
61
3
    }
62
}
63

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

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

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

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

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

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

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

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

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

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

            
190
#[cfg(test)]
191
mod test {
192
    use crate::server_config::{ServerConfig, ServerConfigDataset};
193
    use axum_test::TestServer;
194

            
195
7
    async fn create_server() -> TestServer {
196
7
        let my_app = super::create_app(ServerConfig {
197
7
            datasets: vec![ServerConfigDataset {
198
7
                name: "a".to_string(),
199
7
                url: "localhost:8921".to_string(),
200
7
            }],
201
7
        })
202
7
        .unwrap();
203
7
        let server = TestServer::new(my_app).unwrap();
204
7
        server
205
7
    }
206

            
207
1
    #[tokio::test]
208
1
    async fn route_root() {
209
1
        let server = create_server().await;
210
1
        let response = server.get("/").await;
211
1
        assert_eq!(response.status_code(), 200);
212
    }
213
1
    #[tokio::test]
214
1
    async fn route_dataset() {
215
1
        let server = create_server().await;
216
1
        let response = server.get("/dataset/").await;
217
1
        assert_eq!(response.status_code(), 200);
218
    }
219
1
    #[tokio::test]
220
1
    async fn route_dataset_a() {
221
1
        let server = create_server().await;
222
1
        let response = server.get("/dataset/a").await;
223
1
        assert_eq!(response.status_code(), 200);
224
    }
225
1
    #[tokio::test]
226
1
    async fn route_dataset_a_date() {
227
1
        let server = create_server().await;
228
1
        let response = server.get("/dataset/a/2016-12-20T00:39:57Z").await;
229
1
        assert_eq!(response.status_code(), 200);
230
    }
231
1
    #[tokio::test]
232
1
    async fn route_dataset_a_date_subject() {
233
1
        let server = create_server().await;
234
1
        let response = server.get("/dataset/a/2016-12-20T00:39:57Z/http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23").await;
235
1
        assert_eq!(response.status_code(), 200);
236
    }
237
1
    #[tokio::test]
238
1
    async fn route_crawl() {
239
1
        let server = create_server().await;
240
1
        let response = server.get("/crawl").await;
241
1
        assert_eq!(response.status_code(), 500);
242
    }
243
1
    #[tokio::test]
244
1
    async fn route_dataset_b() {
245
1
        let server = create_server().await;
246
1
        let response = server.get("/dataset/b").await;
247
1
        assert_eq!(response.status_code(), 404);
248
    }
249
}