admin/roles: new role

This commit is contained in:
Philippe Loctaux 2023-05-07 18:15:23 +02:00
parent efaf2bda5a
commit 8fa2fb7ddc
6 changed files with 167 additions and 0 deletions

View file

@ -33,6 +33,7 @@ pub enum Page {
AdminUsersView(AdminUsersView),
AdminUsersNew(AdminUsersNew),
AdminRolesList(AdminRolesList),
AdminRolesNew(AdminRolesNew),
}
impl Page {
@ -62,6 +63,7 @@ impl Page {
Page::AdminUsersView(_) => "pages/admin/users/view",
Page::AdminUsersNew(_) => "pages/admin/users/new",
Page::AdminRolesList(_) => "pages/admin/roles/list",
Page::AdminRolesNew(_) => "pages/admin/roles/new",
}
}
@ -91,6 +93,7 @@ impl Page {
Page::AdminUsersView(_) => "User info",
Page::AdminUsersNew(_) => "New user",
Page::AdminRolesList(_) => "Roles",
Page::AdminRolesNew(_) => "New role",
}
}
@ -122,6 +125,7 @@ impl Page {
Page::AdminUsersView(_) => Some(AdminMenu::Users.into()),
Page::AdminUsersNew(_) => Some(AdminMenu::Users.into()),
Page::AdminRolesList(_) => Some(AdminMenu::Roles.into()),
Page::AdminRolesNew(_) => Some(AdminMenu::Roles.into()),
}
}
@ -151,6 +155,7 @@ impl Page {
Page::AdminUsersView(view) => Box::new(view),
Page::AdminUsersNew(new) => Box::new(new),
Page::AdminRolesList(list) => Box::new(list),
Page::AdminRolesNew(new) => Box::new(new),
}
}
}

View file

@ -38,6 +38,8 @@ pub fn routes() -> Vec<Route> {
admin_users_totp_backup_delete,
admin_users_info_update,
admin_roles_list,
admin_roles_new,
admin_roles_new_form,
]
}
@ -134,4 +136,11 @@ pub mod content {
pub user: JwtClaims,
pub roles: Vec<Role>,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
#[derive(Clone)]
pub struct AdminRolesNew {
pub user: JwtClaims,
}
}

View file

@ -1,6 +1,7 @@
use crate::routes::prelude::*;
use rocket::{get, post};
use roles::Role;
use std::str::FromStr;
#[get("/admin/roles")]
pub async fn admin_roles_list(
@ -23,3 +24,56 @@ pub async fn admin_roles_list(
.map(|flash| Page::with_flash(page.clone(), flash))
.unwrap_or_else(|| page.into()))
}
#[get("/admin/roles/new")]
pub async fn admin_roles_new(admin: JwtAdmin, flash: Option<FlashMessage<'_>>) -> Result<Template> {
let page = Page::AdminRolesNew(super::content::AdminRolesNew { user: admin.0 });
Ok(flash
.map(|flash| Page::with_flash(page.clone(), flash))
.unwrap_or_else(|| page.into()))
}
#[derive(Debug, FromForm)]
pub struct RolesNewForm<'r> {
pub name: &'r str,
pub label: &'r str,
}
#[post("/admin/roles/new", data = "<form>")]
pub async fn admin_roles_new_form(
_admin: JwtAdmin,
mut db: Connection<Database>,
form: Form<RolesNewForm<'_>>,
) -> Result<Flash<Redirect>> {
// Parse name
let name = match RoleID::from_str(form.name) {
Ok(role_id) => role_id,
Err(_) => {
return Ok(Flash::new(
Redirect::to(uri!(admin_roles_new)),
FlashKind::Danger,
"Invalid role name",
));
}
};
let mut transaction = db.begin().await?;
// Insert role in database
if let Err(e) = Role::insert(&mut transaction, &name, form.label).await {
return Ok(Flash::new(
Redirect::to(uri!(admin_roles_new)),
FlashKind::Danger,
e.to_string(),
));
}
transaction.commit().await?;
Ok(Flash::new(
Redirect::to(uri!(admin_roles_list)),
FlashKind::Success,
"Role has been created.",
))
}

View file

@ -0,0 +1,78 @@
{% extends "shell" %}
{% import "utils/form" as form %}
{% block content %}
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">
Admin dashboard
</div>
<h2 class="page-title">
Roles
</h2>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
{% if flash %}
<div class="alert alert-{{flash.0}}" role="alert">
<h4 class="alert-title">{{ flash.1 | safe }}</h4>
</div>
{% endif %}
<form id="new_role" action="" method="post" autocomplete="off" class="card">
<div class="card-header">
<h3 class="card-title">New Role</h3>
</div>
<div class="card-body">
<!-- Name -->
<div class="mb-3">
<label class="form-label required" for="name">Name</label>
<div>
<input name="name" id="name" type="text" placeholder="Enter name"
class="form-control"
required>
<small class="form-hint">
This name identifies the role inside the system. It is not public.<br>
Allowed characters: <code>[a-z][0-9]-</code>
</small>
</div>
</div>
<!-- Label -->
<div class="mb-3">
<label class="form-label required" for="label">Label</label>
<div>
<input name="label" id="label" type="text" placeholder="Enter label"
class="form-control"
required>
<small class="form-hint">
The label is shown to the users.
</small>
</div>
</div>
</div>
<div class="card-footer text-end">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
{{ form::disable_button_delay_submit(form_id="new_role") }}
{% endblock content %}
{% block libs_js %}
{% endblock lib_js %}
{% block additional_js %}
{% endblock additional_js %}

View file

@ -1,6 +1,7 @@
use crate::error::Error;
use crate::Role;
use database::sqlx::SqliteExecutor;
use database::Error as DatabaseError;
use database::Roles as DatabaseRoles;
use id::RoleID;
@ -23,4 +24,21 @@ impl Role {
.map(Self::from)
.collect::<Vec<_>>())
}
pub async fn insert(
conn: impl SqliteExecutor<'_>,
name: &RoleID,
label: &str,
) -> Result<(), Error> {
DatabaseRoles::insert(conn, &name.0, label)
.await
.map_err(|e| match e {
DatabaseError::UniqueConstraintPrimaryKey => {
Error::NameNotAvailable(name.to_string())
}
_ => e.into(),
})?;
Ok(())
}
}

View file

@ -5,4 +5,7 @@
pub enum Error {
#[error("Database: {0}")]
Database(#[from] database::Error),
#[error("The name \"{0}\" is not available.")]
NameNotAvailable(String),
}