settings: add base url, WIP flash system

This commit is contained in:
Philippe Loctaux 2023-03-07 08:42:23 +01:00
parent f2bea92272
commit c670201b86
18 changed files with 190 additions and 68 deletions

View file

@ -9,6 +9,7 @@ rocket_db_pools = { version = "0.1.0-rc.2", features = ["sqlx_sqlite"] }
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] }
infer = { version = "0.12.0", default-features = false }
erased-serde = "0.3"
url = { workspace = true }
# local crates
database_pool = { path = "../database_pool" }

View file

@ -1,10 +1,13 @@
mod content;
mod flash;
mod responder;
mod template;
use self::content::*;
use erased_serde::Serialize;
pub use flash::FlashKind;
pub enum Page {
Error(Error),
Setup,

View file

@ -0,0 +1,29 @@
use std::fmt::{Display, Formatter};
pub enum FlashKind {
Success,
Info,
Warning,
Danger,
}
impl Display for FlashKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
FlashKind::Success => "success",
FlashKind::Info => "info",
FlashKind::Warning => "warning",
FlashKind::Danger => "danger",
}
)
}
}
impl Into<String> for FlashKind {
fn into(self) -> String {
self.to_string()
}
}

View file

@ -1,4 +1,5 @@
use super::Page;
use rocket::request::FlashMessage;
use rocket::serde::Serialize;
use rocket_dyn_templates::Template;
@ -7,20 +8,32 @@ use rocket_dyn_templates::Template;
struct TemplateContent<S: Serialize> {
title: &'static str,
version: &'static str,
flash: Option<(String, String)>,
#[serde(flatten)]
content: S,
}
fn render(p: Page, flash: Option<(String, String)>) -> Template {
Template::render(
p.template_name(),
TemplateContent {
title: p.page_title(),
version: env!("CARGO_PKG_VERSION"),
flash,
content: p.content(),
},
)
}
impl From<Page> for Template {
fn from(p: Page) -> Self {
Self::render(
p.template_name(),
TemplateContent {
title: p.page_title(),
version: env!("CARGO_PKG_VERSION"),
content: p.content(),
},
)
render(p, None)
}
}
impl Page {
pub fn with_flash(self, flash: FlashMessage) -> Template {
render(self, Some(flash.into_inner()))
}
}

View file

@ -8,16 +8,19 @@ pub(self) mod prelude {
pub use crate::error::Error;
pub use crate::file_from_bytes::FileFromBytes;
pub use crate::guards::*;
pub use crate::page::Page;
pub use crate::page::{FlashKind, Page};
pub use hash::Password;
pub use id::UserID;
pub use rocket::form::Form;
pub use rocket::request::FlashMessage;
pub use rocket::response::Flash;
pub use rocket::response::Redirect;
pub use rocket::tokio::task;
pub use rocket::FromForm;
pub use rocket::{routes, uri, Either, Route};
pub use rocket_db_pools::sqlx::Acquire;
pub use rocket_db_pools::Connection;
pub use rocket_dyn_templates::Template;
pub type Result<T> = std::result::Result<T, Error>;
}

View file

@ -1,6 +1,7 @@
use super::prelude::*;
use rocket::{get, post};
use settings::Settings;
use url::Url;
use users::User;
pub fn routes() -> Vec<Route> {
@ -13,14 +14,18 @@ async fn setup_completed(_setup: CompletedSetup) -> Redirect {
}
#[get("/", rank = 2)]
async fn setup() -> Page {
Page::Setup
async fn setup(flash: Option<FlashMessage<'_>>) -> Template {
// TODO: show flash on html page
flash
.map(|flash| Page::with_flash(Page::Setup, flash))
.unwrap_or_else(|| Page::Setup.into())
}
#[derive(Debug, FromForm)]
pub struct CreateFirstAccount<'r> {
pub username: &'r str,
pub password: &'r str,
pub url: &'r str,
}
#[post("/", data = "<form>")]
@ -28,9 +33,18 @@ async fn create_first_account(
form: Form<CreateFirstAccount<'_>>,
_setup: NeedSetup,
mut db: Connection<Database>,
) -> Result<Redirect> {
) -> Result<Either<Redirect, Flash<Redirect>>> {
let form = form.into_inner();
// Parse url
return Ok(Either::Right(Flash::new(
Redirect::to(uri!(self::setup)),
FlashKind::Danger,
"Failed to parse url".to_string(),
)));
let url = Url::parse(form.url).unwrap();
// Generate UserID
let user_id = task::spawn_blocking(UserID::default).await?;
@ -40,7 +54,7 @@ async fn create_first_account(
let mut transaction = db.begin().await?;
// Insert in database
// Insert user in database
User::insert(
&mut transaction,
&user_id,
@ -53,11 +67,14 @@ async fn create_first_account(
// Store UserID in settings
Settings::set_first_admin(&mut transaction, &user_id).await?;
// Store URL in settings
Settings::set_url(&mut transaction, &url).await?;
transaction.commit().await?;
// TODO: login with openid/oauth
Ok(Redirect::to(uri!("/")))
Ok(Either::Left(Redirect::to(uri!("/"))))
}
#[cfg(test)]
@ -77,10 +94,9 @@ mod test {
let create_account = client
.post(uri!("/setup"))
.header(ContentType::Form)
.body(r#"username=phil&password=password"#)
.body(r#"username=phil&password=password&url=https://example.com"#)
.dispatch();
assert_ne!(create_account.status(), Status::UnprocessableEntity);
assert_ne!(create_account.status(), Status::InternalServerError);
assert_eq!(create_account.status(), Status::SeeOther);
// Make request again, make sure its not OK
let setup_page_after_creation = client.get(uri!("/setup")).dispatch();

View file

@ -16,27 +16,35 @@
<h1 class="">Welcome to Ezidam!</h1>
<p class="text-muted">Initial setup</p>
</div>
<!-- First admin account -->
<div class="hr-text hr-text-center hr-text-spaceless">first admin account</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label required" for="username">Username</label>
<input name="username" id="username" type="text" class="form-control" required>
<input name="username" id="username" type="text" placeholder="Enter a username" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label required" for="password">Password</label>
<div class="input-group input-group-flat">
<input name="password" id="password" type="password" class="form-control" autocomplete="off" required>
<input name="password" id="password" type="password" placeholder="Enter password" class="form-control" autocomplete="off" required>
</div>
</div>
</div>
<!-- Settings -->
<div class="hr-text hr-text-center hr-text-spaceless">settings</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label required" for="url">Base URL</label>
<input name="url" id="url" type="url" placeholder="https://example.com" class="form-control" required>
</div>
</div>
</div>
<div class="row align-items-center mt-3">
<div class="col">
<div class="btn-list justify-content-end">
<button type="submit" class="btn btn-primary">Create account</button>
<!-- <a href="#" class="btn btn-primary">-->
<!-- Create account-->
<!-- </a>-->
<button type="submit" class="btn btn-primary">Finish setup</button>
</div>
</div>
</div>