ezidam: branding page: logo update/delete, update business name

This commit is contained in:
Philippe Loctaux 2023-03-25 23:32:46 +01:00
parent d8fe336b06
commit 3532f80882
10 changed files with 165 additions and 52 deletions

View file

@ -20,6 +20,12 @@ impl From<url::ParseError> for Error {
} }
} }
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::internal_server_error(e)
}
}
// Local crates // Local crates
impl From<hash::Error> for Error { impl From<hash::Error> for Error {

View file

@ -35,11 +35,11 @@ impl AdminMenu {
MainItem { MainItem {
id: AdminMenu::Settings.id(), id: AdminMenu::Settings.id(),
label: "Server settings", label: "Server settings",
link: uri!(routes::admin::settings::admin_settings).to_string(), link: uri!(routes::admin::settings::settings_branding).to_string(),
icon: icons::SETTINGS, icon: icons::SETTINGS,
sub: Some(vec![SubItem { sub: Some(vec![SubItem {
label: "Branding", label: "Branding",
link: uri!(routes::admin::settings::admin_settings).to_string(), link: uri!(routes::admin::settings::settings_branding).to_string(),
}]), }]),
}, },
] ]

View file

@ -15,7 +15,7 @@ pub enum Page {
Authorize(Authorize), Authorize(Authorize),
Redirect(Redirect), Redirect(Redirect),
AdminDashboard(AdminDashboard), AdminDashboard(AdminDashboard),
AdminSettings(AdminSettings), AdminSettingsBranding(AdminSettingsBranding),
} }
impl Page { impl Page {
@ -28,7 +28,7 @@ impl Page {
Page::Authorize(_) => "pages/oauth/authorize", Page::Authorize(_) => "pages/oauth/authorize",
Page::Redirect(_) => "pages/oauth/redirect", Page::Redirect(_) => "pages/oauth/redirect",
Page::AdminDashboard(_) => "pages/admin/dashboard", Page::AdminDashboard(_) => "pages/admin/dashboard",
Page::AdminSettings(_) => "pages/admin/settings", Page::AdminSettingsBranding(_) => "pages/admin/settings_branding",
} }
} }
@ -41,7 +41,7 @@ impl Page {
Page::Authorize(_) => "Authorize app", Page::Authorize(_) => "Authorize app",
Page::Redirect(_) => "Redirecting", Page::Redirect(_) => "Redirecting",
Page::AdminDashboard(_) => "Admin dashboard", Page::AdminDashboard(_) => "Admin dashboard",
Page::AdminSettings(_) => "Server settings", Page::AdminSettingsBranding(_) => "Server branding",
} }
} }
@ -56,7 +56,7 @@ impl Page {
Page::Authorize(_) => None, Page::Authorize(_) => None,
Page::Redirect(_) => None, Page::Redirect(_) => None,
Page::AdminDashboard(_) => Some(AdminMenu::Dashboard.into()), Page::AdminDashboard(_) => Some(AdminMenu::Dashboard.into()),
Page::AdminSettings(_) => Some(AdminMenu::Settings.into()), Page::AdminSettingsBranding(_) => Some(AdminMenu::Settings.into()),
} }
} }
@ -69,7 +69,7 @@ impl Page {
Page::Authorize(authorize) => Box::new(authorize), Page::Authorize(authorize) => Box::new(authorize),
Page::Redirect(redirect) => Box::new(redirect), Page::Redirect(redirect) => Box::new(redirect),
Page::AdminDashboard(dashboard) => Box::new(dashboard), Page::AdminDashboard(dashboard) => Box::new(dashboard),
Page::AdminSettings(settings) => Box::new(settings), Page::AdminSettingsBranding(branding) => Box::new(branding),
} }
} }
} }

View file

@ -6,7 +6,7 @@ pub mod dashboard;
pub mod settings; pub mod settings;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![admin_dashboard, admin_settings] routes![admin_dashboard, settings_branding, settings_update_branding]
} }
pub mod content { pub mod content {
@ -23,7 +23,8 @@ pub mod content {
#[derive(Serialize)] #[derive(Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
#[derive(Clone)] #[derive(Clone)]
pub struct AdminSettings { pub struct AdminSettingsBranding {
pub user: JwtClaims, pub user: JwtClaims,
pub business_name: String,
} }
} }

View file

@ -1,9 +1,61 @@
use crate::routes::prelude::*; use crate::routes::prelude::*;
use rocket::get; use rocket::fs::TempFile;
use rocket::{get, post};
use settings::Settings;
#[get("/admin/settings")] #[get("/admin/settings/branding")]
pub async fn admin_settings(mut db: Connection<Database>, admin: JwtAdmin) -> Result<Page> { pub async fn settings_branding(mut db: Connection<Database>, admin: JwtAdmin) -> Result<Page> {
Ok(Page::AdminSettings(super::content::AdminSettings { let settings = Settings::get(&mut *db).await?;
user: admin.0,
})) Ok(Page::AdminSettingsBranding(
super::content::AdminSettingsBranding {
user: admin.0,
business_name: settings.business_name().to_string(),
},
))
}
#[derive(Debug, FromForm)]
pub struct UpdateBranding<'r> {
pub file: TempFile<'r>,
pub business_name: Option<&'r str>,
pub delete_logo: Option<bool>,
}
#[post("/admin/settings/branding", data = "<form>")]
pub async fn settings_update_branding(
mut db: Connection<Database>,
_admin: JwtAdmin,
form: Form<UpdateBranding<'_>>,
) -> Result<Redirect> {
let mut transaction = db.begin().await?;
match form.delete_logo {
Some(delete_logo) => {
if delete_logo {
Settings::delete_business_logo(&mut transaction).await?;
}
}
None => {
if let Some(business_name) = form.business_name {
Settings::set_business_name(&mut transaction, business_name).await?;
}
if form.file.len() != 0 {
let file_path = form.file.path().ok_or_else(|| {
Error::internal_server_error("Failed to get path of uploaded file")
})?;
// Read bytes from file
let file_bytes = rocket::tokio::fs::read(file_path).await?;
// Save in database
Settings::set_business_logo(&mut transaction, &file_bytes).await?;
}
}
}
transaction.commit().await?;
Ok(Redirect::to(uri!(settings_branding)))
} }

View file

@ -1,35 +0,0 @@
{% extends "shell" %}
{% block content %}
<!-- Page header -->
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">
Server settings
</h2>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
<div class="card">
<div class="row g-0">
<div class="col-3 d-none d-md-block border-end">
<div class="card-body">
<div class="list-group list-group-transparent">
<a href="." class="list-group-item list-group-item-action d-flex align-items-center active">Branding</a>
<a href="./security" class="list-group-item list-group-item-action d-flex align-items-center">Security</a>
</div>
</div>
</div>
<div class="col d-flex flex-column">
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,83 @@
{% extends "shell" %}
{% block content %}
<!-- Page header -->
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">
Server settings
</h2>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
<div class="card">
<div class="row g-0">
<div class="col-3 d-none d-md-block border-end">
<div class="card-body">
<div class="list-group list-group-transparent">
<a href="./branding"
class="list-group-item list-group-item-action d-flex align-items-center active">Branding</a>
<a href="./security"
class="list-group-item list-group-item-action d-flex align-items-center">Security</a>
</div>
</div>
</div>
<div class="col d-flex flex-column">
<form action="" method="post" enctype="multipart/form-data">
<div class="card-body">
<h2 class="mb-4">Branding</h2>
<h3 class="card-title">Company logo</h3>
<div class="row align-items-center">
<!-- Current logo -->
<div class="col-auto my-2">
<img src="/logo?time={{ now(timestamp=true) }}" alt="business logo" style="max-width: 13rem; padding: 1rem; background-color: white;">
</div>
<!-- Update logo -->
<div class="col-auto">
<label for="file-input" class="btn">Choose File</label>
<input id="file-input" name="file" type="file" class="d-none">
</div>
<!-- Delete logo -->
<div class="col-auto">
<button class="btn btn-ghost-danger" type="submit" name="delete_logo" value="true">
Delete logo
</button>
</div>
</div>
<!-- Business info -->
<h3 class="card-title mt-4">Business Profile</h3>
<div class="row g-3">
<div class="col-md">
<div class="form-label">Business Name</div>
<input type="text" class="form-control" name="business_name"
value="{{ business_name }}">
</div>
</div>
</div>
<!-- Save -->
<div class="card-footer bg-transparent mt-auto">
<div class="btn-list justify-content-end">
<button class="btn" type="reset">Cancel</button>
<button class="btn btn-primary" type="submit">Submit</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View file

@ -1,3 +1,3 @@
<a href="/" class="navbar-brand navbar-brand-autodark"> <a href="/" class="navbar-brand navbar-brand-autodark">
<img src="/logo" alt="business logo" height="96"> <img src="/logo" alt="business logo" style="max-width: 13rem; padding: 1rem;">
</a> </a>

View file

@ -1,3 +1,3 @@
<a href="/"> <a href="/">
<img src="/logo" alt="business logo" class="navbar-brand-image navbar-brand-autodark"> <img src="/logo" alt="business logo" style="width: 4rem;">
</a> </a>

View file

@ -52,6 +52,12 @@ impl Settings {
Ok(()) Ok(())
} }
pub async fn delete_business_logo(conn: impl SqliteExecutor<'_>) -> Result<(), Error> {
DatabaseSettings::set_business_logo(conn, None).await?;
Ok(())
}
pub async fn set_first_admin(conn: impl SqliteExecutor<'_>, id: &UserID) -> Result<(), Error> { pub async fn set_first_admin(conn: impl SqliteExecutor<'_>, id: &UserID) -> Result<(), Error> {
DatabaseSettings::set_first_admin(conn, &id.0).await?; DatabaseSettings::set_first_admin(conn, &id.0).await?;