mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
some experimentation with frontend
This commit is contained in:
@@ -1,15 +1,19 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustical_frontend"
|
name = "rustical_frontend"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
|
description.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.9"
|
askama = { workspace = true }
|
||||||
askama = { version = "0.12", features = ["serde", "with-actix-web"] }
|
askama_actix = { workspace = true }
|
||||||
askama_actix = "0.14"
|
actix-session = { workspace = true }
|
||||||
tokio = { version = "1.40", features = ["sync", "full"] }
|
serde = { workspace = true }
|
||||||
rustical_store = { path = "../store/" }
|
anyhow = { workspace = true }
|
||||||
anyhow = "1.0"
|
thiserror = { workspace = true }
|
||||||
thiserror = "1.0"
|
tokio = { workspace = true }
|
||||||
actix-session = { version = "0.10", features = ["cookie-session"] }
|
actix-web = { workspace = true }
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
rustical_store = { workspace = true }
|
||||||
|
actix-files = "0.6.6"
|
||||||
|
|||||||
15
crates/frontend/public/asd.html
Normal file
15
crates/frontend/public/asd.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title></title>
|
||||||
|
<link href="style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
ok wow!
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
0
crates/frontend/public/style.css
Normal file
0
crates/frontend/public/style.css
Normal file
6
crates/frontend/src/config.rs
Normal file
6
crates/frontend/src/config.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct FrontendConfig {
|
||||||
|
secret_key: String,
|
||||||
|
}
|
||||||
@@ -1,31 +1,93 @@
|
|||||||
|
use actix_session::{storage::CookieSessionStore, Session, SessionMiddleware};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
|
cookie::Key,
|
||||||
get,
|
get,
|
||||||
http::Method,
|
http::Method,
|
||||||
web::{self, Data},
|
web::{self, Data, Path},
|
||||||
|
Responder,
|
||||||
};
|
};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use rustical_store::CalendarStore;
|
use login::{route_get_login, route_post_login};
|
||||||
|
use rustical_store::{
|
||||||
|
auth::{AuthenticationMiddleware, AuthenticationProvider, User},
|
||||||
|
model::Calendar,
|
||||||
|
CalendarStore,
|
||||||
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod login;
|
||||||
|
|
||||||
|
pub use config::FrontendConfig;
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "index.html")]
|
#[template(path = "index.html")]
|
||||||
struct IndexTemplate {}
|
struct IndexTemplate {}
|
||||||
|
|
||||||
#[get("")]
|
#[get("")]
|
||||||
async fn route_index() -> IndexTemplate {
|
async fn route_index(session: Session) -> IndexTemplate {
|
||||||
|
if let Some(user) = session.get::<User>("user").unwrap() {
|
||||||
|
dbg!(user);
|
||||||
|
} else {
|
||||||
|
session.insert("user", "lennart").unwrap();
|
||||||
|
}
|
||||||
|
dbg!(session.status());
|
||||||
IndexTemplate {}
|
IndexTemplate {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn route_user<C: CalendarStore + ?Sized>(store: Data<RwLock<C>>) -> IndexTemplate {
|
#[derive(Template)]
|
||||||
IndexTemplate {}
|
#[template(path = "components/calendar_list.html")]
|
||||||
|
struct CalendarList {
|
||||||
|
pub owner: String,
|
||||||
|
pub calendars: Vec<Calendar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configure_frontend<C: CalendarStore + ?Sized>(
|
#[derive(Template)]
|
||||||
|
#[template(path = "layouts/default.html")]
|
||||||
|
struct DefaultTemplate<Body: Template> {
|
||||||
|
pub body: Body,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn route_user<C: CalendarStore + ?Sized>(
|
||||||
|
path: Path<String>,
|
||||||
|
store: Data<RwLock<C>>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let store = store.read().await;
|
||||||
|
let owner = path.into_inner();
|
||||||
|
DefaultTemplate {
|
||||||
|
body: CalendarList {
|
||||||
|
owner: owner.to_owned(),
|
||||||
|
calendars: store.get_calendars(&owner).await.unwrap(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_frontend<AP: AuthenticationProvider, C: CalendarStore + ?Sized>(
|
||||||
cfg: &mut web::ServiceConfig,
|
cfg: &mut web::ServiceConfig,
|
||||||
|
auth_provider: Arc<AP>,
|
||||||
store: Arc<RwLock<C>>,
|
store: Arc<RwLock<C>>,
|
||||||
) {
|
) {
|
||||||
cfg.app_data(Data::from(store.clone()))
|
cfg.service(
|
||||||
.service(route_index)
|
web::scope("")
|
||||||
.service(web::resource("/user/{user}").route(web::method(Method::GET).to(route_user::<C>)));
|
.wrap(AuthenticationMiddleware::new(auth_provider.clone()))
|
||||||
|
.wrap(
|
||||||
|
SessionMiddleware::builder(CookieSessionStore::default(), Key::from(&[0; 64]))
|
||||||
|
.cookie_secure(true)
|
||||||
|
.cookie_content_security(actix_session::config::CookieContentSecurity::Private)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.app_data(Data::from(auth_provider))
|
||||||
|
.app_data(Data::from(store.clone()))
|
||||||
|
.service(actix_files::Files::new("/public", "crates/frontend/public").prefer_utf8(true))
|
||||||
|
.service(route_index)
|
||||||
|
.service(
|
||||||
|
web::resource("/user/{user}").route(web::method(Method::GET).to(route_user::<C>)),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/login")
|
||||||
|
.route(web::method(Method::GET).to(route_get_login))
|
||||||
|
.route(web::method(Method::POST).to(route_post_login::<AP>)),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
47
crates/frontend/src/login.rs
Normal file
47
crates/frontend/src/login.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use actix_session::Session;
|
||||||
|
use actix_web::{
|
||||||
|
error::ErrorUnauthorized,
|
||||||
|
web::{Data, Form, Redirect},
|
||||||
|
HttpRequest, HttpResponse, Responder,
|
||||||
|
};
|
||||||
|
use askama::Template;
|
||||||
|
use rustical_store::auth::AuthenticationProvider;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::DefaultTemplate;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "components/login.html")]
|
||||||
|
struct LoginForm;
|
||||||
|
|
||||||
|
pub async fn route_get_login() -> impl Responder {
|
||||||
|
DefaultTemplate { body: LoginForm }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct PostLoginForm {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn route_post_login<AP: AuthenticationProvider>(
|
||||||
|
req: HttpRequest,
|
||||||
|
form: Form<PostLoginForm>,
|
||||||
|
session: Session,
|
||||||
|
auth_provider: Data<AP>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
// TODO: implement auth check
|
||||||
|
dbg!(&form.username, &form.password);
|
||||||
|
if let Ok(Some(user)) = auth_provider
|
||||||
|
.validate_user_token(&form.username, &form.password)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
session.insert("user", user).unwrap();
|
||||||
|
Redirect::to(format!("/frontend/user/{}", &form.username))
|
||||||
|
.see_other()
|
||||||
|
.respond_to(&req)
|
||||||
|
.map_into_boxed_body()
|
||||||
|
} else {
|
||||||
|
ErrorUnauthorized("Unauthorized").error_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
12
crates/frontend/templates/components/calendar_list.html
Normal file
12
crates/frontend/templates/components/calendar_list.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<h2>Welcome {{ owner }}!</h2>
|
||||||
|
<ul>
|
||||||
|
{% for calendar in calendars %}
|
||||||
|
<li>
|
||||||
|
{% if let Some(name) = calendar.displayname %}
|
||||||
|
{{ name }}
|
||||||
|
{% else %}
|
||||||
|
{{ calendar.id }}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
7
crates/frontend/templates/components/login.html
Normal file
7
crates/frontend/templates/components/login.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<form action="login" method="post">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" name="username" placeholder="username">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" placeholder="password">
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
15
crates/frontend/templates/layouts/default.html
Normal file
15
crates/frontend/templates/layouts/default.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>RustiCal</title>
|
||||||
|
<link href="css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{ body|safe }}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user