frontend: Introduce Web Components for forms

This commit is contained in:
Lennart
2025-06-13 18:24:04 +02:00
parent 54997ef865
commit 0c432d70f9
21 changed files with 4533 additions and 218 deletions

View File

@@ -15,7 +15,7 @@ use tower::Service;
#[folder = "public/assets"]
pub struct Assets;
#[derive(Clone)]
#[derive(Clone, Default)]
pub struct EmbedService<E>
where
E: 'static + RustEmbed,
@@ -23,17 +23,6 @@ where
_embed: PhantomData<E>,
}
impl<E> EmbedService<E>
where
E: 'static + RustEmbed,
{
pub fn new() -> Self {
Self {
_embed: PhantomData,
}
}
}
impl<E> Service<Request> for EmbedService<E>
where
E: 'static + RustEmbed,

View File

@@ -26,14 +26,9 @@ pub use config::FrontendConfig;
use oidc_user_store::OidcUserStore;
use crate::routes::{
addressbook::{
route_addressbook, route_addressbook_restore, route_create_addressbook,
route_delete_addressbook,
},
addressbook::{route_addressbook, route_addressbook_restore, route_delete_addressbook},
app_token::{route_delete_app_token, route_post_app_token},
calendar::{
route_calendar, route_calendar_restore, route_create_calendar, route_delete_calendar,
},
calendar::{route_calendar, route_calendar_restore, route_delete_calendar},
login::{route_get_login, route_post_login, route_post_logout},
user::{route_get_home, route_root, route_user_named},
};
@@ -61,7 +56,6 @@ pub fn frontend_router<AP: AuthenticationProvider, CS: CalendarStore, AS: Addres
post(route_delete_app_token::<AP>),
)
// Calendar
.route("/user/{user}/calendar", post(route_create_calendar::<CS>))
.route(
"/user/{user}/calendar/{calendar}",
get(route_calendar::<CS>),
@@ -75,10 +69,6 @@ pub fn frontend_router<AP: AuthenticationProvider, CS: CalendarStore, AS: Addres
post(route_calendar_restore::<CS>),
)
// Addressbook
.route(
"/user/{user}/addressbook",
post(route_create_addressbook::<AS>),
)
.route(
"/user/{user}/addressbook/{addressbook}",
get(route_addressbook::<AS>),
@@ -95,7 +85,7 @@ pub fn frontend_router<AP: AuthenticationProvider, CS: CalendarStore, AS: Addres
.route("/logout", post(route_post_logout));
#[cfg(not(feature = "dev"))]
let mut router = router.route_service("/assets/{*file}", EmbedService::<Assets>::new());
let mut router = router.route_service("/assets/{*file}", EmbedService::<Assets>::default());
#[cfg(feature = "dev")]
let mut router = router.nest_service(
"/assets",

View File

@@ -33,53 +33,6 @@ pub async fn route_addressbook<AS: AddressbookStore>(
.into_response())
}
fn empty_to_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let val: Option<String> = Deserialize::deserialize(deserializer)?;
Ok(val.filter(|val| !val.is_empty()))
}
#[derive(Deserialize, Clone)]
pub struct PutAddressbookForm {
id: String,
#[serde(deserialize_with = "empty_to_none")]
displayname: Option<String>,
#[serde(deserialize_with = "empty_to_none")]
description: Option<String>,
}
pub async fn route_create_addressbook<AS: AddressbookStore>(
Path(owner): Path<String>,
Extension(store): Extension<Arc<AS>>,
user: User,
Form(PutAddressbookForm {
id,
displayname,
description,
}): Form<PutAddressbookForm>,
) -> Result<Response, rustical_store::Error> {
if !user.is_principal(&owner) {
return Ok(StatusCode::UNAUTHORIZED.into_response());
}
assert!(!id.is_empty());
let addressbook = Addressbook {
id: id.to_owned(),
displayname,
description,
principal: user.id.to_owned(),
synctoken: 0,
deleted_at: None,
push_topic: uuid::Uuid::new_v4().to_string(),
};
store.insert_addressbook(addressbook).await?;
Ok(Redirect::to(&format!("/frontend/user/{}/addressbook/{}", user.id, id)).into_response())
}
pub async fn route_addressbook_restore<AS: AddressbookStore>(
Path((owner, addressbook_id)): Path<(String, String)>,
Extension(store): Extension<Arc<AS>>,

View File

@@ -3,16 +3,14 @@ use std::sync::Arc;
use askama::Template;
use askama_web::WebTemplate;
use axum::{
Extension, Form,
Extension,
extract::Path,
response::{IntoResponse, Redirect, Response},
};
use axum_extra::TypedHeader;
use headers::Referer;
use http::StatusCode;
use rustical_ical::CalendarObjectType;
use rustical_store::{Calendar, CalendarStore, auth::User};
use serde::{Deserialize, Deserializer};
#[derive(Template, WebTemplate)]
#[template(path = "pages/calendar.html")]
@@ -34,82 +32,6 @@ pub async fn route_calendar<C: CalendarStore>(
.into_response())
}
fn empty_to_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let val: Option<String> = Deserialize::deserialize(deserializer)?;
Ok(val.filter(|val| !val.is_empty()))
}
#[derive(Deserialize, Clone)]
pub struct PutCalendarForm {
id: String,
#[serde(deserialize_with = "empty_to_none")]
displayname: Option<String>,
#[serde(deserialize_with = "empty_to_none")]
description: Option<String>,
#[serde(deserialize_with = "empty_to_none")]
color: Option<String>,
#[serde(deserialize_with = "empty_to_none")]
subscription_url: Option<String>,
comp_event: Option<String>,
comp_todo: Option<String>,
comp_journal: Option<String>,
}
pub async fn route_create_calendar<C: CalendarStore>(
Path(owner): Path<String>,
Extension(store): Extension<Arc<C>>,
user: User,
Form(PutCalendarForm {
id,
displayname,
description,
color,
subscription_url,
comp_event,
comp_todo,
comp_journal,
}): Form<PutCalendarForm>,
) -> Result<Response, rustical_store::Error> {
if !user.is_principal(&owner) {
return Ok(StatusCode::UNAUTHORIZED.into_response());
}
assert!(!id.is_empty());
let mut comps = vec![];
if comp_event.is_some() {
comps.push(CalendarObjectType::Event);
}
if comp_todo.is_some() {
comps.push(CalendarObjectType::Todo);
}
if comp_journal.is_some() {
comps.push(CalendarObjectType::Journal);
}
let cal = Calendar {
id: id.to_owned(),
displayname,
description,
color,
subscription_url,
principal: user.id.to_owned(),
components: comps,
order: 0,
timezone_id: None,
timezone: None,
synctoken: 0,
deleted_at: None,
push_topic: uuid::Uuid::new_v4().to_string(),
};
store.insert_calendar(cal).await?;
Ok(Redirect::to(&format!("/frontend/user/{}/calendar/{}", user.id, id)).into_response())
}
pub async fn route_calendar_restore<CS: CalendarStore>(
Path((owner, cal_id)): Path<(String, String)>,
Extension(store): Extension<Arc<CS>>,