// extern crate actix; extern crate actix_files; extern crate actix_web; extern crate serde; #[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_json; #[macro_use] extern crate ledb; #[macro_use] extern crate ledb_derive; extern crate env_logger; extern crate ledb_types; #[macro_use] extern crate tera; use tera::Context; // extern crate futures; // extern crate tokio; use actix_web::{ error, guard, http::header, http::Method, middleware::Logger, middleware::NormalizePath, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder, Result, }; use std::cell::Cell; use rand::seq::IteratorRandom; use std::time::Instant; use std::{fs, io}; // use tokio::spawn; // use futures::{Future}; // use actix::Actor; // use actix::System; //use crate::tokio::prelude::Future; //use ledb_actix::{Document, Options, Storage, StorageAddrExt}; use ledb::{Options, Storage}; #[derive(Debug, Default, Clone, Serialize, Deserialize, Document)] struct BatchInfo { #[document(primary)] #[serde(default)] filename: String, #[serde(default)] description: String, #[serde(default)] author: String, #[serde(default)] comment: String, #[serde(default)] url: String, #[serde(default)] date: String, #[serde(default)] processed_by: String, #[serde(default)] redacted_by: String, #[serde(default)] copyright: String, #[serde(default)] theme: String, #[serde(default)] kind: String, #[serde(default)] source: String, #[serde(default)] rating: String, } #[derive(Debug, Default, Clone, Serialize, Deserialize, Document)] struct Question { #[document(primary)] #[serde(default)] num: u32, #[document(index)] id: String, description: String, answer: String, #[serde(default)] author: String, #[serde(default)] comment: String, #[serde(default)] comment1: String, #[serde(default)] tour: String, #[serde(default)] url: String, #[serde(default)] date: String, #[serde(default)] processed_by: String, #[serde(default)] redacted_by: String, #[serde(default)] copyright: String, #[serde(default)] theme: String, #[serde(default)] kind: String, #[serde(default)] source: String, #[serde(default)] rating: String, #[document(nested)] #[serde(default)] batch_info: BatchInfo, } struct AppState { storage: Storage, template: tera::Tera, } fn get_question(storage: &Storage, id: u32) -> Result, Error> { if 0 == id { return Ok(None); } let collection = storage.collection("questions").unwrap(); let last_id = collection.last_id().unwrap(); if id > last_id { Err(Error::from(())) } else { let question = collection.get::(id); if question.is_err() { Err(Error::from(())) } else { Ok(question.unwrap()) } } } fn show_question_details(template_file: &str, data: web::Data, id: web::Path) -> Result { let id = id.into_inner(); let question = get_question(&data.storage, id); if question.is_ok() { let question = question.unwrap(); if question.is_some() { let question = question.unwrap(); let body = data.template.render(template_file, &question).unwrap(); Ok(HttpResponse::Ok().content_type("text/html").body(body)) } else { Ok(HttpResponse::Found() .header(header::LOCATION, "/q/") .finish()) } } else { let context = Context::new(); Ok(HttpResponse::with_body( actix_web::http::StatusCode::NOT_FOUND, actix_web::dev::Body::from(data.template.render("404.html", &context).unwrap()), )) } } fn show_question(data: web::Data, id: web::Path) -> Result { show_question_details("question.html", data, id) } fn show_answer(data: web::Data, id: web::Path) -> Result { show_question_details("answer.html", data, id) } fn index(data: web::Data, req: HttpRequest) -> Result { let collection = data.storage.collection("questions").unwrap(); let mut rng = rand::thread_rng(); let last_id = collection.last_id().unwrap(); let id = (1..(last_id + 1)).choose(&mut rng).unwrap(); let url = req.url_for("question", &[format!("{}", id)])?; Ok(HttpResponse::Found() .header(header::LOCATION, url.as_str()) .finish()) } fn main() { std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); let options: Options = serde_json::from_value(json!({ "read_only": true, "no_lock": true, })) .unwrap(); let storage = Storage::new("db", options).unwrap(); HttpServer::new(move || { let data = AppState { storage: storage.clone(), template: compile_templates!("./templates/**/*"), }; App::new() .wrap(Logger::default()) .data(data) .route("/q", web::to(index)) .service( web::scope("/q") .service(actix_files::Files::new("/static", "./static")) .service( web::resource("/{id}") .name("question") // <- set resource name, then it could be used in `url_for` .guard(guard::Get()) .to(show_question), ) .service( web::resource("/{id}/a/") .name("answer") // <- set resource name, then it could be used in `url_for` .guard(guard::Get()) .to(show_answer), ) .route("/", web::to(index)) ) .route("/", web::to(index)) }) .bind("127.0.0.1:8088") .unwrap() .run() .unwrap(); }