use actix_session::Session;
use actix_web::{
error,
http::{header::ContentType, StatusCode},
HttpResponse,
};
use async_trait::async_trait;
use derive_more::{Display, Error};
use sea_orm::DatabaseConnection;
use serde_derive::Serialize;
use std::collections::{BTreeMap, HashMap};
use std::fmt::{self, Display as FmtDisplay};
use tera::Tera;
pub mod builder;
pub mod csrf;
pub mod model;
pub mod routes;
pub mod tera_templates;
pub mod view_model;
pub mod prelude {
pub use crate::builder::{ActixAdminBuilder, ActixAdminBuilderTrait};
pub use crate::csrf::{
csrf_token_for, verify_csrf, CsrfError, CSRF_HEADER, CSRF_QUERY_PARAM, CSRF_SESSION_KEY,
};
pub use crate::model::{
ActixAdminModel, ActixAdminModelFilter, ActixAdminModelFilterTrait,
ActixAdminModelFilterType, ActixAdminModelTrait, ActixAdminModelValidationTrait, FilterFn,
};
pub use crate::routes::{
bulk_action, create_or_edit_post, get_admin_ctx, ActixAdminBulkActionDispatch, SortOrder,
};
pub use crate::view_model::{
ActixAdminBulkAction, ActixAdminFilterOperator, ActixAdminPrimaryKey, ActixAdminViewModel,
ActixAdminViewModelField, ActixAdminViewModelFieldType, ActixAdminViewModelFilter,
ActixAdminViewModelParams, ActixAdminViewModelSerializable, ActixAdminViewModelTrait,
};
pub use crate::{hashmap, ActixAdminSelectListTrait};
pub use crate::{ActixAdmin, ActixAdminConfiguration, ActixAdminError, ActixAdminErrorType};
pub use actix_admin_macros::{
DeriveActixAdmin, DeriveActixAdminEnumSelectList, DeriveActixAdminModel,
DeriveActixAdminModelSelectList, DeriveActixAdminViewModel,
};
pub use actix_session::Session;
pub use async_trait::async_trait;
}
use crate::prelude::*;
#[doc(hidden)]
#[macro_export]
macro_rules! hashmap {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key.to_string(), $val); )*
map
}}
}
#[async_trait]
pub trait ActixAdminSelectListTrait {
async fn get_key_value(
db: &DatabaseConnection,
tenant_ref: Option<i32>,
) -> core::result::Result<Vec<(String, String)>, ActixAdminError>;
}
#[derive(Clone)]
pub struct ActixAdminConfiguration {
pub enable_auth: bool,
pub user_is_logged_in: Option<for<'a> fn(&'a Session) -> bool>,
pub user_tenant_ref: Option<for<'a> fn(&'a Session) -> Option<i32>>,
pub login_link: Option<String>,
pub logout_link: Option<String>,
pub file_upload_directory: &'static str,
pub navbar_title: &'static str,
pub base_path: &'static str,
pub custom_css_paths: Option<Vec<String>>,
pub custom_js_paths: Option<Vec<String>>,
pub enable_csrf: bool,
}
impl Default for ActixAdminConfiguration {
fn default() -> Self {
Self {
enable_auth: false,
user_is_logged_in: None,
user_tenant_ref: None,
login_link: None,
logout_link: None,
file_upload_directory: "./file_uploads",
navbar_title: "Actix Admin",
base_path: "/admin",
custom_css_paths: None,
custom_js_paths: None,
enable_csrf: true,
}
}
}
#[derive(Clone)]
pub struct ActixAdmin {
pub entity_names: BTreeMap<String, Vec<ActixAdminMenuElement>>,
pub view_models: HashMap<String, ActixAdminViewModel>,
pub card_grids: HashMap<String, Vec<Vec<String>>>,
pub configuration: ActixAdminConfiguration,
pub tera: Tera,
pub support_path: Option<String>,
}
#[derive(PartialEq, Eq, Clone, Serialize)]
pub struct ActixAdminMenuElement {
pub name: String,
pub link: String,
pub is_custom_handler: bool,
}
#[derive(Debug, Error)]
pub struct ActixAdminError {
pub ty: ActixAdminErrorType,
pub msg: String,
}
impl FmtDisplay for ActixAdminError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "{}: {}", self.ty, self.msg)
}
}
#[derive(Debug, Display, Error, PartialEq, Eq)]
pub enum ActixAdminErrorType {
#[display("Internal error")]
InternalError,
#[display("Form has validation errors")]
ValidationErrors,
#[display("Bad request")]
BadRequest,
#[display("Unauthorized")]
Unauthorized,
#[display("Forbidden")]
Forbidden,
#[display("Could not list entities")]
ListError,
#[display("Could not create entity")]
CreateError,
#[display("Could not delete entity")]
DeleteError,
#[display("Could not edit entity")]
EditError,
#[display("Database error")]
DatabaseError,
#[display("Entity does not exist")]
EntityDoesNotExistError,
#[display("Upload error")]
UploadError,
#[display("IO error")]
IoError,
#[display("CSRF token missing or invalid")]
CsrfError,
#[display("Unknown bulk action")]
UnknownBulkAction,
}
impl ActixAdminError {
pub fn new(ty: ActixAdminErrorType, msg: impl Into<String>) -> Self {
Self {
ty,
msg: msg.into(),
}
}
pub fn bad_request(msg: impl Into<String>) -> Self {
Self::new(ActixAdminErrorType::BadRequest, msg)
}
pub fn not_found(msg: impl Into<String>) -> Self {
Self::new(ActixAdminErrorType::EntityDoesNotExistError, msg)
}
pub fn internal(msg: impl Into<String>) -> Self {
Self::new(ActixAdminErrorType::InternalError, msg)
}
}
impl error::ResponseError for ActixAdminError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}
fn status_code(&self) -> StatusCode {
use ActixAdminErrorType::*;
match self.ty {
BadRequest | ValidationErrors => StatusCode::BAD_REQUEST,
Unauthorized => StatusCode::UNAUTHORIZED,
Forbidden | CsrfError => StatusCode::FORBIDDEN,
EntityDoesNotExistError | UnknownBulkAction => StatusCode::NOT_FOUND,
InternalError | ListError | CreateError | DeleteError | EditError | DatabaseError
| UploadError | IoError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
macro_rules! impl_from_error {
($($err:ty => $ty:ident),* $(,)?) => {
$(
impl From<$err> for ActixAdminError {
fn from(err: $err) -> Self {
Self { ty: ActixAdminErrorType::$ty, msg: err.to_string() }
}
}
)*
};
}
impl_from_error! {
sea_orm::DbErr => DatabaseError,
std::io::Error => IoError,
actix_multipart::MultipartError => UploadError,
serde_urlencoded::de::Error => BadRequest,
}
#[derive(Debug, Display, Serialize)]
pub enum ActixAdminNotificationType {
#[display("is-danger")]
Danger,
}
#[derive(Debug, Serialize)]
pub struct ActixAdminNotification {
css_class: String,
message: String,
}
impl From<ActixAdminError> for ActixAdminNotification {
fn from(e: ActixAdminError) -> Self {
Self {
css_class: ActixAdminNotificationType::Danger.to_string(),
message: e.to_string(),
}
}
}