1use actix_session::Session;
8use actix_web::{
9 error,
10 http::{header::ContentType, StatusCode},
11 HttpResponse,
12};
13use async_trait::async_trait;
14use derive_more::{Display, Error};
15use sea_orm::DatabaseConnection;
16use serde_derive::Serialize;
17use std::collections::{BTreeMap, HashMap};
18use std::fmt::{self, Display as FmtDisplay};
19use tera::Tera;
20
21pub mod builder;
22pub mod csrf;
23pub mod model;
24pub mod routes;
25pub mod tera_templates;
26pub mod view_model;
27
28pub mod prelude {
29 pub use crate::builder::{ActixAdminBuilder, ActixAdminBuilderTrait};
30 pub use crate::csrf::{
31 csrf_token_for, verify_csrf, CsrfError, CSRF_HEADER, CSRF_QUERY_PARAM, CSRF_SESSION_KEY,
32 };
33 pub use crate::model::{
34 ActixAdminModel, ActixAdminModelFilter, ActixAdminModelFilterTrait,
35 ActixAdminModelFilterType, ActixAdminModelTrait, ActixAdminModelValidationTrait, FilterFn,
36 };
37 pub use crate::routes::{
38 bulk_action, create_or_edit_post, get_admin_ctx, ActixAdminBulkActionDispatch, SortOrder,
39 };
40 pub use crate::view_model::{
41 ActixAdminBulkAction, ActixAdminFilterOperator, ActixAdminPrimaryKey, ActixAdminViewModel,
42 ActixAdminViewModelField, ActixAdminViewModelFieldType, ActixAdminViewModelFilter,
43 ActixAdminViewModelParams, ActixAdminViewModelSerializable, ActixAdminViewModelTrait,
44 };
45 pub use crate::{hashmap, ActixAdminSelectListTrait};
46 pub use crate::{ActixAdmin, ActixAdminConfiguration, ActixAdminError, ActixAdminErrorType};
47 pub use actix_admin_macros::{
48 DeriveActixAdmin, DeriveActixAdminEnumSelectList, DeriveActixAdminModel,
49 DeriveActixAdminModelSelectList, DeriveActixAdminViewModel,
50 };
51 pub use actix_session::Session;
52 pub use async_trait::async_trait;
53}
54
55use crate::prelude::*;
56
57#[doc(hidden)]
58#[macro_export]
59macro_rules! hashmap {
60 ($( $key: expr => $val: expr ),*) => {{
61 let mut map = ::std::collections::HashMap::new();
62 $( map.insert($key.to_string(), $val); )*
63 map
64 }}
65}
66
67#[async_trait]
69pub trait ActixAdminSelectListTrait {
70 async fn get_key_value(
71 db: &DatabaseConnection,
72 tenant_ref: Option<i32>,
73 ) -> core::result::Result<Vec<(String, String)>, ActixAdminError>;
74}
75
76#[derive(Clone)]
77pub struct ActixAdminConfiguration {
78 pub enable_auth: bool,
79 pub user_is_logged_in: Option<for<'a> fn(&'a Session) -> bool>,
80 pub user_tenant_ref: Option<for<'a> fn(&'a Session) -> Option<i32>>,
81 pub login_link: Option<String>,
82 pub logout_link: Option<String>,
83 pub file_upload_directory: &'static str,
84 pub navbar_title: &'static str,
85 pub base_path: &'static str,
86 pub custom_css_paths: Option<Vec<String>>,
87 pub custom_js_paths: Option<Vec<String>>,
88 pub enable_csrf: bool,
97}
98
99impl Default for ActixAdminConfiguration {
100 fn default() -> Self {
101 Self {
102 enable_auth: false,
103 user_is_logged_in: None,
104 user_tenant_ref: None,
105 login_link: None,
106 logout_link: None,
107 file_upload_directory: "./file_uploads",
108 navbar_title: "Actix Admin",
109 base_path: "/admin",
110 custom_css_paths: None,
111 custom_js_paths: None,
112 enable_csrf: true,
113 }
114 }
115}
116
117#[derive(Clone)]
118pub struct ActixAdmin {
119 pub entity_names: BTreeMap<String, Vec<ActixAdminMenuElement>>,
120 pub view_models: HashMap<String, ActixAdminViewModel>,
121 pub card_grids: HashMap<String, Vec<Vec<String>>>,
122 pub configuration: ActixAdminConfiguration,
123 pub tera: Tera,
124 pub support_path: Option<String>,
125}
126
127#[derive(PartialEq, Eq, Clone, Serialize)]
128pub struct ActixAdminMenuElement {
129 pub name: String,
130 pub link: String,
131 pub is_custom_handler: bool,
132}
133
134#[derive(Debug, Error)]
135pub struct ActixAdminError {
136 pub ty: ActixAdminErrorType,
137 pub msg: String,
138}
139
140impl FmtDisplay for ActixAdminError {
141 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
142 write!(formatter, "{}: {}", self.ty, self.msg)
143 }
144}
145
146#[derive(Debug, Display, Error, PartialEq, Eq)]
148pub enum ActixAdminErrorType {
149 #[display("Internal error")]
150 InternalError,
151
152 #[display("Form has validation errors")]
153 ValidationErrors,
154
155 #[display("Bad request")]
156 BadRequest,
157
158 #[display("Unauthorized")]
159 Unauthorized,
160
161 #[display("Forbidden")]
162 Forbidden,
163
164 #[display("Could not list entities")]
165 ListError,
166
167 #[display("Could not create entity")]
168 CreateError,
169
170 #[display("Could not delete entity")]
171 DeleteError,
172
173 #[display("Could not edit entity")]
174 EditError,
175
176 #[display("Database error")]
177 DatabaseError,
178
179 #[display("Entity does not exist")]
180 EntityDoesNotExistError,
181
182 #[display("Upload error")]
183 UploadError,
184
185 #[display("IO error")]
186 IoError,
187
188 #[display("CSRF token missing or invalid")]
189 CsrfError,
190
191 #[display("Unknown bulk action")]
192 UnknownBulkAction,
193}
194
195impl ActixAdminError {
196 pub fn new(ty: ActixAdminErrorType, msg: impl Into<String>) -> Self {
197 Self {
198 ty,
199 msg: msg.into(),
200 }
201 }
202
203 pub fn bad_request(msg: impl Into<String>) -> Self {
204 Self::new(ActixAdminErrorType::BadRequest, msg)
205 }
206
207 pub fn not_found(msg: impl Into<String>) -> Self {
208 Self::new(ActixAdminErrorType::EntityDoesNotExistError, msg)
209 }
210
211 pub fn internal(msg: impl Into<String>) -> Self {
212 Self::new(ActixAdminErrorType::InternalError, msg)
213 }
214}
215
216impl error::ResponseError for ActixAdminError {
217 fn error_response(&self) -> HttpResponse {
218 HttpResponse::build(self.status_code())
219 .insert_header(ContentType::html())
220 .body(self.to_string())
221 }
222
223 fn status_code(&self) -> StatusCode {
224 use ActixAdminErrorType::*;
225 match self.ty {
226 BadRequest | ValidationErrors => StatusCode::BAD_REQUEST,
227 Unauthorized => StatusCode::UNAUTHORIZED,
228 Forbidden | CsrfError => StatusCode::FORBIDDEN,
229 EntityDoesNotExistError | UnknownBulkAction => StatusCode::NOT_FOUND,
230 InternalError | ListError | CreateError | DeleteError | EditError | DatabaseError
231 | UploadError | IoError => StatusCode::INTERNAL_SERVER_ERROR,
232 }
233 }
234}
235
236macro_rules! impl_from_error {
237 ($($err:ty => $ty:ident),* $(,)?) => {
238 $(
239 impl From<$err> for ActixAdminError {
240 fn from(err: $err) -> Self {
241 Self { ty: ActixAdminErrorType::$ty, msg: err.to_string() }
242 }
243 }
244 )*
245 };
246}
247
248impl_from_error! {
249 sea_orm::DbErr => DatabaseError,
250 std::io::Error => IoError,
251 actix_multipart::MultipartError => UploadError,
252 serde_urlencoded::de::Error => BadRequest,
253}
254
255#[derive(Debug, Display, Serialize)]
257pub enum ActixAdminNotificationType {
258 #[display("is-danger")]
259 Danger,
260}
261
262#[derive(Debug, Serialize)]
263pub struct ActixAdminNotification {
264 css_class: String,
265 message: String,
266}
267
268impl From<ActixAdminError> for ActixAdminNotification {
269 fn from(e: ActixAdminError) -> Self {
270 Self {
271 css_class: ActixAdminNotificationType::Danger.to_string(),
272 message: e.to_string(),
273 }
274 }
275}