diff --git a/src/main.rs b/src/main.rs index 87329c8..d055d41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ pub mod demo; pub mod errors; mod routes; mod settings; +mod static_assets; #[cfg(test)] mod tests; mod utils; diff --git a/src/routes.rs b/src/routes.rs index 379fa85..a440914 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -20,4 +20,5 @@ use crate::api::v1; pub fn services(cfg: &mut web::ServiceConfig) { v1::services(cfg); + crate::static_assets::services(cfg); } diff --git a/src/static_assets/filemap.rs b/src/static_assets/filemap.rs new file mode 100644 index 0000000..cd8cd29 --- /dev/null +++ b/src/static_assets/filemap.rs @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use cache_buster::Files; + +pub struct FileMap { + pub files: Files, +} + +impl FileMap { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let map = include_str!("../cache_buster_data.json"); + let files = Files::new(map); + Self { files } + } + pub fn get(&self, path: impl AsRef) -> Option<&str> { + let file_path = self.files.get_full_path(path); + file_path.map(|file_path| &file_path[1..]) + } +} + +#[cfg(test)] +mod tests { + + #[test] + fn filemap_works() { + let files = super::FileMap::new(); + let css = files.get("./static/cache/css/main.css").unwrap(); + println!("{}", css); + assert!(css.contains("/assets/css/main")); + } +} diff --git a/src/static_assets/mod.rs b/src/static_assets/mod.rs new file mode 100644 index 0000000..90f6350 --- /dev/null +++ b/src/static_assets/mod.rs @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use actix_web::*; + +pub mod filemap; +pub mod static_files; + +pub use filemap::FileMap; +pub use routes::{Assets, ASSETS}; + +pub fn services(cfg: &mut web::ServiceConfig) { + cfg.service(static_files::static_files); +} + +pub mod routes { + use lazy_static::lazy_static; + use serde::*; + + use super::*; + + lazy_static! { + pub static ref ASSETS: Assets = Assets::new(); + } + + #[derive(Serialize)] + /// Top-level routes data structure for V1 AP1 + pub struct Assets { + /// Authentication routes + pub css: &'static str, + } + + impl Assets { + /// create new instance of Routes + pub fn new() -> Assets { + Assets { + css: &static_files::assets::CSS, + } + } + } +} diff --git a/src/static_assets/static_files.rs b/src/static_assets/static_files.rs new file mode 100644 index 0000000..81375cd --- /dev/null +++ b/src/static_assets/static_files.rs @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use std::borrow::Cow; + +use actix_web::body::BoxBody; +use actix_web::{get, http::header, web, HttpResponse, Responder}; +use mime_guess::from_path; +use rust_embed::RustEmbed; + +use crate::CACHE_AGE; + +pub mod assets { + use crate::FILES; + use lazy_static::lazy_static; + + lazy_static! { + pub static ref CSS: &'static str = FILES.get("./static/cache/css/main.css").unwrap(); + } +} + +#[derive(RustEmbed)] +#[folder = "assets/"] +struct Asset; + +fn handle_assets(path: &str) -> HttpResponse { + match Asset::get(path) { + Some(content) => { + let body: BoxBody = match content.data { + Cow::Borrowed(bytes) => BoxBody::new(bytes), + Cow::Owned(bytes) => BoxBody::new(bytes), + }; + + HttpResponse::Ok() + .insert_header(header::CacheControl(vec![ + header::CacheDirective::Public, + header::CacheDirective::Extension("immutable".into(), None), + header::CacheDirective::MaxAge(CACHE_AGE), + ])) + .content_type(from_path(path).first_or_octet_stream().as_ref()) + .body(body) + } + None => HttpResponse::NotFound().body("404 Not Found"), + } +} + +#[get("/assets/{_:.*}")] +pub async fn static_files(path: web::Path) -> impl Responder { + handle_assets(&path) +} + +#[cfg(test)] +mod tests { + use actix_web::http::StatusCode; + use actix_web::test; + + use crate::data::Data; + use crate::db::BoxDB; + use crate::tests::*; + use crate::*; + + use super::assets::CSS; + + #[actix_rt::test] + async fn postgrest_static_files_works() { + let (db, data) = sqlx_postgres::get_data().await; + static_assets_work(data, db).await; + } + + #[actix_rt::test] + async fn sqlite_static_files_works() { + let (db, data) = sqlx_sqlite::get_data().await; + static_assets_work(data, db).await; + } + + async fn static_assets_work(data: Arc, db: BoxDB) { + let app = get_app!(data, db).await; + + for file in [*CSS].iter() { + let resp = get_request!(&app, file); + assert_eq!(resp.status(), StatusCode::OK); + } + } +}