use actix_web::{get, http, web, HttpResponse}; use actix_web_static_files::ResourceFiles; use askama_actix::TemplateToResponse; use diesel::prelude::*; use r2d2_redis::redis; use std::ops::DerefMut; use crate::{cache, config, db, markdown}; pub mod templates; pub mod static_dir { include!(concat!(env!("OUT_DIR"), "/generated.rs")); } async fn not_found() -> HttpResponse { let mut resp = templates::StatusCode { status_code: http::StatusCode::NOT_FOUND, message: Some("maybe try a correct url?".to_string()), } .to_response(); *resp.status_mut() = http::StatusCode::NOT_FOUND; resp } #[get("/posts")] async fn posts(db_pool: web::Data) -> HttpResponse { let db_conn = &mut match db_pool.get() { Ok(x) => x, Err(e) => return HttpResponse::InternalServerError().body(format!("{:?}", e)), }; let posts = match db::schema::posts::table .filter(db::schema::posts::active) .order(db::schema::posts::published_at.desc()) .load::(db_conn) { Ok(x) => x, Err(e) => { return HttpResponse::InternalServerError().body(format!("{:?}", e)); } }; templates::Posts { posts }.to_response() } #[get("/")] async fn index() -> HttpResponse { templates::Index.to_response() } #[get("/about")] async fn about() -> HttpResponse { templates::About.to_response() } #[get("/posts/{slug}")] async fn post_by_slug( db_pool: web::Data, redis_pool: web::Data, path: web::Path, ) -> HttpResponse { let slug = path.into_inner(); let db_conn = &mut match db_pool.get() { Ok(x) => x, Err(e) => return HttpResponse::InternalServerError().body(format!("{:?}", e)), }; let redis_conn = &mut match redis_pool.get() { Ok(x) => x, Err(e) => return HttpResponse::InternalServerError().body(format!("{:?}", e)), }; let post_stripped: Option<(i32, String)> = match db::schema::posts::table .select((db::schema::posts::id, db::schema::posts::name)) .filter(db::schema::posts::slug.eq(&slug)) .filter(db::schema::posts::active) .get_result::<(i32, String)>(db_conn) .optional() { Ok(x) => x, Err(e) => { return HttpResponse::InternalServerError().body(format!("{:?}", e)); } }; match post_stripped { Some(stripped) => { let (stripped_id, stripped_name) = stripped; let key = cache::keys::post_content(stripped_id); match match redis::cmd("GET") .arg(&key) .query::>(redis_conn.deref_mut()) { Ok(x) => x, Err(e) => return HttpResponse::InternalServerError().body(format!("{:?}", e)), } { Some(s) => templates::PostBySlug { name: stripped_name, slug, content: s, } .to_response(), None => { let post = match db::schema::posts::table .filter(db::schema::posts::id.eq(stripped_id)) .first::(db_conn) { Ok(x) => x, Err(e) => { return HttpResponse::InternalServerError().body(format!("{:?}", e)); } }; let html = markdown::to_html(&post.content); match redis::cmd("SET") .arg(&key) .arg(&html) .query::>(redis_conn.deref_mut()) { Ok(x) => x, Err(e) => { return HttpResponse::InternalServerError().body(format!("{:?}", e)) } }; if let Err(e) = redis::cmd("EXPIRE") .arg(key) .arg(config::CONFIG.cache_post_content_ttl) .query::<()>(redis_conn.deref_mut()) { return HttpResponse::InternalServerError().body(format!("{:?}", e)); } templates::PostBySlug { name: post.name, slug: post.slug, content: html, } .to_response() } } } None => { let mut resp = templates::StatusCode { status_code: http::StatusCode::NOT_FOUND, message: Some("this post does not exists... yet".to_string()), } .to_response(); *resp.status_mut() = http::StatusCode::NOT_FOUND; resp } } } fn setup_routes(cfg: &mut web::ServiceConfig) { let generated = static_dir::generate(); cfg.service(index) .service(about) .service(posts) .service(ResourceFiles::new("/static", generated)) .service(post_by_slug) .default_service(web::route().to(not_found)); } pub fn init(cfg: &mut web::ServiceConfig) { cfg.app_data(web::Data::new(db::pool().unwrap())) .app_data(web::Data::new(cache::pool().unwrap())); setup_routes(cfg); }