parent
1c015c3b83
commit
e17681d6f5
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,7 @@ |
|||||||
[package] |
[workspace] |
||||||
name = "yopa-test" |
|
||||||
version = "0.1.0" |
|
||||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] |
|
||||||
edition = "2018" |
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
members = [ |
||||||
|
"yopa", |
||||||
[dependencies] |
"yopa-web", |
||||||
log = "0.4.13" |
#"yopa-test", |
||||||
simple-logging = "2.0.2" |
] |
||||||
|
|
||||||
yopa = { path = "./yopa", features = [ "uuid-ids" ] } |
|
||||||
|
|
||||||
serde_json = "1.0.61" |
|
||||||
serde = { version = "1.0.120", features = ["derive"] } |
|
||||||
|
|
||||||
anyhow = "1.0.38" |
|
||||||
thiserror = "1.0.23" |
|
||||||
|
@ -0,0 +1,19 @@ |
|||||||
|
[package] |
||||||
|
name = "yopa-test" |
||||||
|
version = "0.1.0" |
||||||
|
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] |
||||||
|
edition = "2018" |
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
log = "0.4.13" |
||||||
|
simple-logging = "2.0.2" |
||||||
|
|
||||||
|
yopa = { path = "../yopa", features = [ "uuid-ids" ] } |
||||||
|
|
||||||
|
serde_json = "1.0.61" |
||||||
|
serde = { version = "1.0.120", features = ["derive"] } |
||||||
|
|
||||||
|
anyhow = "1.0.38" |
||||||
|
thiserror = "1.0.23" |
@ -0,0 +1,21 @@ |
|||||||
|
[package] |
||||||
|
name = "yopa-web" |
||||||
|
version = "0.1.0" |
||||||
|
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] |
||||||
|
edition = "2018" |
||||||
|
build = "build.rs" |
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
log = "0.4.14" |
||||||
|
simple-logging = "2.0.2" |
||||||
|
actix-web = "3" |
||||||
|
parking_lot = "0.11.1" |
||||||
|
include_dir = "0.6.0" |
||||||
|
tera = "1.6.1" |
||||||
|
actix-web-static-files = "3.0" |
||||||
|
lazy_static = "1.4.0" |
||||||
|
|
||||||
|
[build-dependencies] |
||||||
|
actix-web-static-files = "3.0" |
@ -0,0 +1,14 @@ |
|||||||
|
use actix_web_static_files::resource_dir; |
||||||
|
use std::path::Path; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
// Magic to bundle static files for actix to serve
|
||||||
|
|
||||||
|
let out_dir = std::env::var("OUT_DIR").unwrap(); |
||||||
|
let out_path = Path::new(&out_dir); |
||||||
|
|
||||||
|
resource_dir("./resources/static") |
||||||
|
.with_generated_filename(out_path.join("static_files.rs")) |
||||||
|
.with_generated_fn("included_static_files") |
||||||
|
.build().unwrap(); |
||||||
|
} |
@ -0,0 +1,200 @@ |
|||||||
|
*, *::before, *::after { |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
html, textarea, select { |
||||||
|
font-family: "IBM Plex", "DejaVu Sans", "Helvetica", sans-serif; |
||||||
|
} |
||||||
|
|
||||||
|
.Form { |
||||||
|
display: block; |
||||||
|
width: 900px; |
||||||
|
margin: 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
nav.top-nav { |
||||||
|
margin-bottom: .5rem; |
||||||
|
border-bottom: 1px solid silver; |
||||||
|
} |
||||||
|
|
||||||
|
nav.top-nav, .content { |
||||||
|
margin: 0 auto; |
||||||
|
width: 900px; |
||||||
|
} |
||||||
|
|
||||||
|
a { |
||||||
|
color: gray; |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
|
||||||
|
a:hover { |
||||||
|
color: black; |
||||||
|
text-decoration: underline; |
||||||
|
} |
||||||
|
|
||||||
|
nav.top-nav a { |
||||||
|
display: inline-block; |
||||||
|
padding: .75rem; |
||||||
|
|
||||||
|
color: gray; |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
|
||||||
|
nav.top-nav a:hover { |
||||||
|
color: black; |
||||||
|
text-decoration: underline; |
||||||
|
} |
||||||
|
|
||||||
|
.Form .Row { |
||||||
|
display: flex; |
||||||
|
padding: .25rem; |
||||||
|
} |
||||||
|
|
||||||
|
.Form .Row.indented { |
||||||
|
padding-left: 10.25rem; |
||||||
|
} |
||||||
|
|
||||||
|
input[type="text"], |
||||||
|
input[type="number"], |
||||||
|
textarea, |
||||||
|
.tag-input { |
||||||
|
border: 1px solid silver; |
||||||
|
padding: 0.5rem; |
||||||
|
border-radius: 5px; |
||||||
|
font-size: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
input[type="text"]:focus, |
||||||
|
input[type="number"]:focus, |
||||||
|
textarea:focus, |
||||||
|
.tag-input.active { |
||||||
|
box-shadow: inset 0 0 0 1px #3c97ff; |
||||||
|
border-color: #3c97ff; |
||||||
|
outline: 0 none !important; |
||||||
|
} |
||||||
|
|
||||||
|
.Form label { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 10rem; |
||||||
|
height: 2.1rem; |
||||||
|
line-height: 2.1rem; |
||||||
|
vertical-align: middle; |
||||||
|
text-align: right; |
||||||
|
display: inline-block; |
||||||
|
padding-right: .5rem; |
||||||
|
align-self: flex-start; |
||||||
|
} |
||||||
|
|
||||||
|
.Form input[type="text"], |
||||||
|
.Form input[type="number"], |
||||||
|
.Form select { |
||||||
|
height: 2.1rem; |
||||||
|
width: 15rem; |
||||||
|
} |
||||||
|
|
||||||
|
.Form textarea { |
||||||
|
flex-shrink: 1; |
||||||
|
width: 30rem; |
||||||
|
height: 6rem; |
||||||
|
} |
||||||
|
|
||||||
|
.Form label.checkbox-wrap { |
||||||
|
width: 15rem; |
||||||
|
padding-right: 1rem; |
||||||
|
text-align: left !important; |
||||||
|
} |
||||||
|
|
||||||
|
.tag-input { |
||||||
|
position: relative; |
||||||
|
width: 30rem; |
||||||
|
padding-bottom: 0rem !important; |
||||||
|
} |
||||||
|
|
||||||
|
.tag-input input, |
||||||
|
.tag-input input:focus { |
||||||
|
border: 0 transparent; |
||||||
|
padding: 0; |
||||||
|
margin: 0; |
||||||
|
box-shadow: none; |
||||||
|
outline: 0 none !important; |
||||||
|
} |
||||||
|
|
||||||
|
.cards-table { |
||||||
|
border-collapse: collapse; |
||||||
|
margin: 0 auto; |
||||||
|
margin-top: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cards-table .actions { |
||||||
|
font-size: 90%; |
||||||
|
} |
||||||
|
|
||||||
|
.cards-table td, |
||||||
|
.cards-table th { |
||||||
|
padding: .5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cards-table thead th { |
||||||
|
border-bottom: 2px solid silver; |
||||||
|
} |
||||||
|
|
||||||
|
.cards-table tbody td { |
||||||
|
border-bottom: 1px solid silver; |
||||||
|
} |
||||||
|
|
||||||
|
.cards-table tbody tr:last-child td { |
||||||
|
border-bottom: 2px solid silver; |
||||||
|
} |
||||||
|
|
||||||
|
.cards-table td.tags { |
||||||
|
padding: .25rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cards-table .tag { |
||||||
|
background: #E2E1DF; |
||||||
|
font-size: 90%; |
||||||
|
padding: 0.25rem .45rem; |
||||||
|
border-radius: 3px; |
||||||
|
display: inline-block; |
||||||
|
} |
||||||
|
|
||||||
|
.paginate { |
||||||
|
margin: 1rem auto; |
||||||
|
width: 300px; |
||||||
|
text-align: center; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
|
||||||
|
.paginate span, |
||||||
|
.paginate a { |
||||||
|
padding: .5rem 1rem; |
||||||
|
border-radius: .5rem; |
||||||
|
border: 1px solid silver; |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
|
||||||
|
.paginate span.num { |
||||||
|
border: 1px solid #ccc; |
||||||
|
color: gray; |
||||||
|
} |
||||||
|
|
||||||
|
.paginate a { |
||||||
|
cursor: pointer; |
||||||
|
user-select: none; |
||||||
|
} |
||||||
|
|
||||||
|
.paginate a:hover { |
||||||
|
background: #ccc; |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
|
||||||
|
.paginate .disabled { |
||||||
|
opacity: .5; |
||||||
|
cursor: default; |
||||||
|
} |
||||||
|
|
||||||
|
.paginate .disabled:hover { |
||||||
|
color: gray; |
||||||
|
background: transparent; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,18 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<title>{% block title %}{% endblock title %} • YOPA</title> |
||||||
|
<link rel="stylesheet" href="/static/style.css"> |
||||||
|
<link rel="stylesheet" href="/static/taggle.css"> |
||||||
|
<script src="/static/taggle.min.js"></script> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<nav class="top-nav"> |
||||||
|
{% block nav %}{% endblock %} |
||||||
|
</nav> |
||||||
|
<div class="content"> |
||||||
|
{% block content %}{% endblock %} |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,17 @@ |
|||||||
|
{% extends "_layout" %} |
||||||
|
|
||||||
|
{% block title -%} |
||||||
|
Index |
||||||
|
{%- endblock %} |
||||||
|
|
||||||
|
{% block nav -%} |
||||||
|
<a href="/">Home</a> |
||||||
|
{%- endblock %} |
||||||
|
|
||||||
|
{% block content -%} |
||||||
|
|
||||||
|
<h1>Welcome to tera on actix</h1> |
||||||
|
|
||||||
|
Number of visits: {{visits}} |
||||||
|
|
||||||
|
{%- endblock %} |
@ -0,0 +1,69 @@ |
|||||||
|
#[macro_use] extern crate log; |
||||||
|
#[macro_use] extern crate lazy_static; |
||||||
|
|
||||||
|
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, HttpRequest}; |
||||||
|
use parking_lot::Mutex; |
||||||
|
use actix_web::web::{service, scope}; |
||||||
|
use actix_web::http::StatusCode; |
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering}; |
||||||
|
use std::path::{PathBuf, Path}; |
||||||
|
use actix_web_static_files; |
||||||
|
use tera::Tera; |
||||||
|
use include_dir::Dir; |
||||||
|
use std::sync::Arc; |
||||||
|
use log::LevelFilter; |
||||||
|
|
||||||
|
mod tera_ext; |
||||||
|
|
||||||
|
// Embed static files
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/static_files.rs")); |
||||||
|
|
||||||
|
// Embed templates
|
||||||
|
static TEMPLATES: include_dir::Dir = include_dir::include_dir!("./resources/templates"); |
||||||
|
|
||||||
|
lazy_static! { |
||||||
|
static ref TERA : Tera = { |
||||||
|
let mut tera = Tera::default(); |
||||||
|
tera_ext::tera_includedir_walk_folder(&mut tera, &TEMPLATES); |
||||||
|
tera |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
struct VisitCounter { |
||||||
|
pub counter : AtomicUsize, |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitCounter { |
||||||
|
pub fn count_visit(&self) -> usize { |
||||||
|
self.counter.fetch_add(1, Ordering::Relaxed) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[get("/")] |
||||||
|
async fn service_index(req: HttpRequest, visits : web::Data<VisitCounter>) -> actix_web::Result<impl Responder> { |
||||||
|
let mut context = tera::Context::new(); |
||||||
|
context.insert("visits", &visits.count_visit()); |
||||||
|
|
||||||
|
let html = TERA.render("index", &context).map_err(|e| { |
||||||
|
actix_web::error::ErrorInternalServerError(e) |
||||||
|
})?; |
||||||
|
Ok(HttpResponse::Ok().body(html)) |
||||||
|
} |
||||||
|
|
||||||
|
#[actix_web::main] |
||||||
|
async fn main() -> std::io::Result<()> { |
||||||
|
simple_logging::log_to_stderr(LevelFilter::Debug); |
||||||
|
|
||||||
|
let counter = web::Data::new(VisitCounter { counter : Default::default() }); |
||||||
|
|
||||||
|
HttpServer::new(move || { |
||||||
|
let static_files = actix_web_static_files::ResourceFiles::new("/static", included_static_files()) |
||||||
|
.do_not_resolve_defaults(); |
||||||
|
|
||||||
|
App::new() |
||||||
|
.app_data(counter.clone()) |
||||||
|
.service(service_index) |
||||||
|
.service(static_files) |
||||||
|
}) |
||||||
|
.bind("127.0.0.1:8080")?.run().await |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
|
||||||
|
|
||||||
|
// #[get("/")]
|
||||||
|
// async fn hello(state : web::Data<VisitCounter>) -> impl Responder {
|
||||||
|
// HttpResponse::Ok().body(format!("Hello world! {}", state.count_visit()))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[post("/echo")]
|
||||||
|
// async fn echo(req_body: String) -> impl Responder {
|
||||||
|
// HttpResponse::Ok().body(req_body)
|
||||||
|
// }
|
||||||
|
// async fn static_files(req: HttpRequest) -> actix_web::Result<NamedFile> {
|
||||||
|
// let path: PathBuf = req.match_info().query("filename").parse().unwrap();
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Ok(NamedFile::open(path)?)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[get("/users/{user_id}/{friend}")] // <- define path parameters
|
||||||
|
// async fn user_friend(web::Path((user_id, friend)): web::Path<(u32, String)>, state : web::Data<VisitCounter>) -> impl Responder {
|
||||||
|
// HttpResponse::Ok()
|
||||||
|
// .header("Content-Type", "text/html")
|
||||||
|
// .body(format!("<h1>Welcome {}, user_id {}!</h1>Visitor nr {}\n", friend, user_id, state.count_visit()))
|
||||||
|
// }
|
@ -0,0 +1,30 @@ |
|||||||
|
use tera::Tera; |
||||||
|
use include_dir::Dir; |
||||||
|
|
||||||
|
/// Add templates from include_dir!() to Tera
|
||||||
|
pub(crate) fn tera_includedir_walk_folder(tera : &mut Tera, dir: &Dir) { |
||||||
|
let mut templates = vec![]; |
||||||
|
tera_includedir_walk_folder_inner(&mut templates, tera, dir); |
||||||
|
tera.add_raw_templates(templates); |
||||||
|
} |
||||||
|
|
||||||
|
fn tera_includedir_walk_folder_inner(collected : &mut Vec<(String, String)>, tera : &mut Tera, dir: &Dir) { |
||||||
|
for f in dir.files() { |
||||||
|
if f.path().extension().unwrap_or_default() != "tera" { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
let name = f.path().file_stem().unwrap().to_str().unwrap().split('.').nth(0).unwrap(); |
||||||
|
let content = f.contents_utf8().unwrap(); |
||||||
|
|
||||||
|
let p = f.path().parent().unwrap().join(name); |
||||||
|
let template_path = p.to_str().unwrap(); |
||||||
|
|
||||||
|
debug!("Add template: {}", template_path); |
||||||
|
collected.push((template_path.to_string(), content.to_string())); |
||||||
|
} |
||||||
|
|
||||||
|
for subdir in dir.dirs() { |
||||||
|
tera_includedir_walk_folder_inner(collected, tera, subdir) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue