use std::sync::Arc;
use axum::http::Method;
use sea_orm::{ActiveModelTrait, ActiveValue::Set, EntityTrait, PaginatorTrait, QuerySelect};
use crate::admin::{
forms::DroitAdminForm,
helper::{
dyn_form::DynForm,
resource_entry::{
CountFn, CreateFn, DeleteFn, FormBuilder, GetFn, ListFn, ListParams, ResourceEntry,
SortDir, UpdateFn,
},
sql_dialect::{ilike, text_eq},
},
resource::AdminResource,
};
use crate::forms::field::RuniqueForm;
use crate::utils::{
aliases::{ADb, ATera, StrMap},
constante::admin_context::{
common::RESOURCE_KEY,
permission::{
CAN_CREATE, CAN_DELETE, CAN_DELETE_OWN, CAN_READ, CAN_UPDATE, CAN_UPDATE_OWN, DROITS,
GROUPE_ID, GROUPES,
},
},
forms::parse_bool,
trad::{t, tf},
};
pub(super) fn encode_droit_id(groupe_id: i32, resource_key: &str) -> String {
format!("{}:{}", groupe_id, resource_key)
}
fn decode_droit_id(id: &str) -> Result<(i32, String), sea_orm::DbErr> {
let mut parts = id.splitn(2, ':');
let groupe_id = parts
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(|| sea_orm::DbErr::Custom(t("admin.builtin.invalid_id").into_owned()))?;
let resource_key = parts
.next()
.ok_or_else(|| sea_orm::DbErr::Custom(t("admin.builtin.invalid_id").into_owned()))?
.to_string();
Ok((groupe_id, resource_key))
}
pub(super) fn droit_entry() -> ResourceEntry {
use crate::auth::permissions::groupes_droits;
let meta = AdminResource::new(
DROITS,
"runique::auth::permissions::groupes_droits::Model",
"DroitAdminForm",
DROITS,
)
.parent_scope(GROUPES, GROUPE_ID, Some(RESOURCE_KEY));
let form_builder: FormBuilder = Arc::new(
|db: ADb,
_vec: Vec<std::string::String>,
data: StrMap,
tera: ATera,
csrf: String,
method: Method| {
Box::pin(async move {
let mut form = DroitAdminForm::build_with_data(&data, tera, &csrf, method).await;
let submitted_groupe = data.get(GROUPE_ID).cloned().unwrap_or_default();
let groupes = crate::auth::permissions::groupe::Entity::find()
.all(&*db)
.await
.unwrap_or_default();
let groupe_choices = groupes
.into_iter()
.map(|g| crate::forms::fields::ChoiceOption::new(&g.id.to_string(), &g.nom))
.collect::<Vec<_>>();
let mut gf = crate::forms::fields::ChoiceField::new(GROUPE_ID)
.choices(groupe_choices)
.label(t("admin.group").as_ref())
.required();
{
use crate::forms::base::FormField;
if !submitted_groupe.is_empty() {
gf.set_value(&submitted_groupe);
}
}
form.get_form_mut()
.fields
.insert(GROUPE_ID.to_string(), Box::new(gf));
let submitted_keys = data.get(RESOURCE_KEY).cloned().unwrap_or_default();
let resource_choices = _vec
.into_iter()
.map(|key| crate::forms::fields::ChoiceOption::new(&key, &key))
.collect::<Vec<_>>();
let mut rf = crate::forms::fields::CheckboxField::new(RESOURCE_KEY)
.choices(resource_choices)
.label(t("admin.resources").as_ref());
{
use crate::forms::base::FormField;
if !submitted_keys.is_empty() {
rf.set_value(&submitted_keys);
}
}
form.get_form_mut()
.fields
.insert(RESOURCE_KEY.to_string(), Box::new(rf));
Box::new(DroitDynWrapper(form)) as Box<dyn DynForm>
})
},
);
let edit_form_builder: FormBuilder = Arc::new(
|db: ADb,
_vec: Vec<std::string::String>,
data: StrMap,
tera: ATera,
csrf: String,
method: Method| {
Box::pin(async move {
let mut form = DroitAdminForm::build_with_data(&data, tera, &csrf, method).await;
let current_groupe = data.get(GROUPE_ID).cloned().unwrap_or_default();
let groupes = crate::auth::permissions::groupe::Entity::find()
.all(&*db)
.await
.unwrap_or_default();
let groupe_choices = groupes
.into_iter()
.map(|g| {
let id_str = g.id.to_string();
let mut opt = crate::forms::fields::ChoiceOption::new(&id_str, &g.nom);
if id_str == current_groupe {
opt = opt.selected();
}
opt
})
.collect::<Vec<_>>();
let mut gf = crate::forms::fields::ChoiceField::new(GROUPE_ID)
.choices(groupe_choices)
.label(t("admin.group").as_ref())
.required();
{
use crate::forms::base::FormField;
gf.set_value(¤t_groupe);
}
form.get_form_mut()
.fields
.insert(GROUPE_ID.to_string(), Box::new(gf));
let current_key = data.get(RESOURCE_KEY).cloned().unwrap_or_default();
let resource_choices = _vec
.into_iter()
.map(|key| {
let mut opt = crate::forms::fields::ChoiceOption::new(&key, &key);
if key == current_key {
opt = opt.selected();
}
opt
})
.collect::<Vec<_>>();
let mut rf = crate::forms::fields::CheckboxField::new(RESOURCE_KEY)
.choices(resource_choices)
.label(t("admin.resource").as_ref());
{
use crate::forms::base::FormField;
rf.set_value(¤t_key);
}
form.get_form_mut()
.fields
.insert(RESOURCE_KEY.to_string(), Box::new(rf));
Box::new(DroitDynWrapper(form)) as Box<dyn DynForm>
})
},
);
let list_fn: ListFn = Arc::new(|db: ADb, params: ListParams| {
Box::pin(async move {
use crate::auth::permissions::groupe;
use sea_orm::ColumnTrait;
use sea_orm::{
QueryFilter, QueryOrder,
sea_query::{Alias, Expr, Order},
};
use std::collections::HashMap as HMap;
let mut query = groupes_droits::Entity::find();
const SORT_COLS: &[&str] = &[
"groupe_id",
"resource_key",
"can_create",
"can_read",
"can_update",
"can_delete",
"can_update_own",
"can_delete_own",
];
if let Some(ref col) = params.sort_by
&& SORT_COLS.contains(&col.as_str())
{
let order = if params.sort_dir == SortDir::Desc {
Order::Desc
} else {
Order::Asc
};
query = query.order_by(Expr::col(Alias::new(col.as_str())), order);
}
if let Some((col, val)) = ¶ms.scope
&& col.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_')
{
query = query.filter(text_eq(&db, col.as_str(), val));
}
if let Some(ref search_str) = params.search {
let pattern = format!("%{}%", search_str.to_lowercase());
let mut cond = sea_orm::Condition::any();
cond = cond.add(ilike(&db, "resource_key", &pattern));
query = query.filter(cond);
}
let rows = query
.offset(params.offset)
.limit(params.limit)
.all(&*db)
.await?;
let groupe_ids: Vec<i32> = rows.iter().map(|r| r.groupe_id).collect();
let groupes: HMap<i32, String> = groupe::Entity::find()
.filter(groupe::Column::Id.is_in(groupe_ids))
.all(&*db)
.await
.unwrap_or_default()
.into_iter()
.map(|g| (g.id, g.nom))
.collect();
Ok(rows
.into_iter()
.map(|r| {
let mut v = serde_json::to_value(&r).unwrap_or(serde_json::Value::Null);
if let serde_json::Value::Object(ref mut map) = v {
let id_str = encode_droit_id(r.groupe_id, &r.resource_key);
map.insert("id".to_string(), serde_json::Value::String(id_str));
let nom = groupes.get(&r.groupe_id).cloned().unwrap_or_default();
map.insert(GROUPES.to_string(), serde_json::Value::String(nom));
}
v
})
.collect())
})
});
let count_fn: CountFn = Arc::new(|db: ADb, _search, _column_filters, scope| {
Box::pin(async move {
use sea_orm::QueryFilter;
let mut query = groupes_droits::Entity::find();
if let Some(ref search_str) = _search {
let pattern = format!("%{}%", search_str.to_lowercase());
query = query.filter(ilike(&db, "resource_key", &pattern));
}
if let Some((col, val)) = &scope
&& col.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_')
{
query = query.filter(text_eq(&db, col.as_str(), val));
}
query.count(&*db).await
})
});
let get_fn: GetFn = Arc::new(|db: ADb, id: String| {
Box::pin(async move {
use sea_orm::{ColumnTrait, QueryFilter};
let Ok((groupe_id, resource_key)) = decode_droit_id(&id) else {
return Ok(None);
};
let row = groupes_droits::Entity::find()
.filter(groupes_droits::Column::GroupeId.eq(groupe_id))
.filter(groupes_droits::Column::ResourceKey.eq(&resource_key))
.one(&*db)
.await?;
let Some(row) = row else { return Ok(None) };
let mut value = serde_json::to_value(&row).unwrap_or(serde_json::Value::Null);
if let serde_json::Value::Object(ref mut map) = value {
map.insert("id".to_string(), serde_json::Value::String(id));
}
Ok(Some(value))
})
});
let delete_fn: DeleteFn = Arc::new(|db: ADb, id: String| {
Box::pin(async move {
use sea_orm::{ColumnTrait, QueryFilter};
let (groupe_id, resource_key) = decode_droit_id(&id)?;
groupes_droits::Entity::delete_many()
.filter(groupes_droits::Column::GroupeId.eq(groupe_id))
.filter(groupes_droits::Column::ResourceKey.eq(resource_key))
.exec(&*db)
.await
.map(|_| ())
})
});
let create_fn: CreateFn = Arc::new(|db: ADb, data: StrMap| {
Box::pin(async move {
let groupe_id: i32 = data
.get(GROUPE_ID)
.and_then(|s| s.parse().ok())
.ok_or_else(|| {
sea_orm::DbErr::Custom(t("admin.builtin.invalid_id").into_owned())
})?;
let resource_keys_str = data.get(RESOURCE_KEY).cloned().unwrap_or_default();
let resource_keys: Vec<&str> = resource_keys_str
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.collect();
if resource_keys.is_empty() {
return Err(sea_orm::DbErr::Custom(
t("admin.builtin.droit_resource_required").into_owned(),
));
}
for resource_key in &resource_keys {
groupes_droits::ActiveModel {
groupe_id: Set(groupe_id),
resource_key: Set(resource_key.to_string()),
can_create: Set(parse_bool(&data, CAN_CREATE)),
can_read: Set(parse_bool(&data, CAN_READ)),
can_update: Set(parse_bool(&data, CAN_UPDATE)),
can_delete: Set(parse_bool(&data, CAN_DELETE)),
can_update_own: Set(parse_bool(&data, CAN_UPDATE_OWN)),
can_delete_own: Set(parse_bool(&data, CAN_DELETE_OWN)),
}
.insert(&*db)
.await
.map_err(|e| {
if super::is_unique_violation(&e) {
sea_orm::DbErr::Custom(tf("admin.builtin.droit_exists", &[resource_key]))
} else {
e
}
})?;
}
Ok(())
})
});
let update_fn: UpdateFn = Arc::new(|db: ADb, id: String, data: StrMap| {
Box::pin(async move {
use sea_orm::{ColumnTrait, QueryFilter, TransactionTrait};
let (old_groupe_id, old_resource_key) = decode_droit_id(&id)?;
let new_groupe_id: i32 = data
.get(GROUPE_ID)
.and_then(|s| s.parse().ok())
.unwrap_or(old_groupe_id);
let new_resource_key = data
.get(RESOURCE_KEY)
.and_then(|s| s.split(',').find(|k| !k.trim().is_empty()))
.unwrap_or(&old_resource_key)
.trim()
.to_string();
let txn = db.begin().await?;
groupes_droits::Entity::delete_many()
.filter(groupes_droits::Column::GroupeId.eq(old_groupe_id))
.filter(groupes_droits::Column::ResourceKey.eq(&old_resource_key))
.exec(&txn)
.await?;
groupes_droits::ActiveModel {
groupe_id: Set(new_groupe_id),
resource_key: Set(new_resource_key),
can_create: Set(parse_bool(&data, CAN_CREATE)),
can_read: Set(parse_bool(&data, CAN_READ)),
can_update: Set(parse_bool(&data, CAN_UPDATE)),
can_delete: Set(parse_bool(&data, CAN_DELETE)),
can_update_own: Set(parse_bool(&data, CAN_UPDATE_OWN)),
can_delete_own: Set(parse_bool(&data, CAN_DELETE_OWN)),
}
.insert(&txn)
.await
.map_err(|e| {
if super::is_unique_violation(&e) {
sea_orm::DbErr::Custom(t("admin.builtin.droit_exists").into_owned())
} else {
e
}
})?;
txn.commit().await
})
});
let partial_update_fn: UpdateFn = Arc::new(|db: ADb, id: String, data: StrMap| {
Box::pin(async move {
use sea_orm::{ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, QueryFilter};
let (groupe_id, resource_key) = decode_droit_id(&id)?;
let model = groupes_droits::Entity::find()
.filter(groupes_droits::Column::GroupeId.eq(groupe_id))
.filter(groupes_droits::Column::ResourceKey.eq(&resource_key))
.one(&*db)
.await?
.ok_or_else(|| sea_orm::DbErr::RecordNotFound(format!("droit not found: {id}")))?;
let mut active: groupes_droits::ActiveModel = model.into();
if data.contains_key(CAN_CREATE) {
active.can_create = Set(parse_bool(&data, CAN_CREATE));
} else {
active.can_create = NotSet;
}
if data.contains_key(CAN_READ) {
active.can_read = Set(parse_bool(&data, CAN_READ));
} else {
active.can_read = NotSet;
}
if data.contains_key(CAN_UPDATE) {
active.can_update = Set(parse_bool(&data, CAN_UPDATE));
} else {
active.can_update = NotSet;
}
if data.contains_key(CAN_DELETE) {
active.can_delete = Set(parse_bool(&data, CAN_DELETE));
} else {
active.can_delete = NotSet;
}
if data.contains_key(CAN_UPDATE_OWN) {
active.can_update_own = Set(parse_bool(&data, CAN_UPDATE_OWN));
} else {
active.can_update_own = NotSet;
}
if data.contains_key(CAN_DELETE_OWN) {
active.can_delete_own = Set(parse_bool(&data, CAN_DELETE_OWN));
} else {
active.can_delete_own = NotSet;
}
active.update(&*db).await?;
Ok(())
})
});
ResourceEntry::new(meta, form_builder)
.with_edit_form_builder(edit_form_builder)
.with_list_fn(list_fn)
.with_count_fn(count_fn)
.with_get_fn(get_fn)
.with_delete_fn(delete_fn)
.with_create_fn(create_fn)
.with_update_fn(update_fn)
.with_partial_update_fn(partial_update_fn)
}
struct DroitDynWrapper(pub DroitAdminForm);
#[async_trait::async_trait]
impl DynForm for DroitDynWrapper {
async fn is_valid(&mut self) -> bool {
self.0.is_valid().await
}
async fn save(&mut self, _db: &crate::utils::aliases::ADb) -> Result<(), sea_orm::DbErr> {
Ok(())
}
fn get_form(&self) -> &crate::forms::form::Forms {
self.0.get_form()
}
fn get_form_mut(&mut self) -> &mut crate::forms::form::Forms {
self.0.get_form_mut()
}
}