1use crate::routes::{
2 create_get, create_post, delete, delete_many, download, edit_get, edit_post, index, list,
3 not_found, show,
4};
5use crate::{
6 prelude::*,
7 routes::{
8 bulk_action, delete_file, display_card_grid, export_csv, search,
9 ActixAdminBulkActionDispatch,
10 },
11 ActixAdminMenuElement,
12};
13use actix_web::{web, Route};
14use std::collections::{BTreeMap, HashMap};
15use std::fs;
16
17pub struct ActixAdminBuilder {
24 pub scopes: HashMap<String, actix_web::Scope>,
25 pub custom_routes: Vec<(String, Route)>,
26 pub actix_admin: ActixAdmin,
27 pub custom_index: Option<Route>,
28}
29
30pub trait ActixAdminBuilderTrait {
36 fn new(configuration: ActixAdminConfiguration) -> Self;
37 fn get_scope(self) -> actix_web::Scope;
38 fn get_actix_admin(&self) -> ActixAdmin;
39}
40
41impl ActixAdminBuilderTrait for ActixAdminBuilder {
42 fn new(configuration: ActixAdminConfiguration) -> Self {
43 Self::new(configuration)
44 }
45 fn get_scope(self) -> actix_web::Scope {
46 Self::get_scope(self)
47 }
48 fn get_actix_admin(&self) -> ActixAdmin {
49 Self::get_actix_admin(self)
50 }
51}
52
53impl ActixAdminBuilder {
54 pub fn new(configuration: ActixAdminConfiguration) -> Self {
55 ActixAdminBuilder {
56 actix_admin: ActixAdmin {
57 entity_names: BTreeMap::new(),
58 view_models: HashMap::new(),
59 card_grids: HashMap::new(),
60 configuration,
61 tera: crate::tera_templates::get_tera(),
62 support_path: None,
63 },
64 custom_routes: Vec::new(),
65 scopes: HashMap::new(),
66 custom_index: None,
67 }
68 }
69
70 pub fn add_entity<E: ActixAdminViewModelTrait + 'static>(
71 &mut self,
72 view_model: &ActixAdminViewModel,
73 ) {
74 self.add_entity_to_category::<E>(view_model, "");
75 }
76
77 pub fn add_entity_to_category<E: ActixAdminViewModelTrait + 'static>(
78 &mut self,
79 view_model: &ActixAdminViewModel,
80 category_name: &str,
81 ) {
82 self.scopes.insert(
83 E::get_entity_name(),
84 web::scope(&format!("/{}", E::get_entity_name()))
85 .route("/list", web::get().to(list::<E>))
86 .route("/export_csv", web::get().to(export_csv::<E>))
87 .route("/create", web::get().to(create_get::<E>))
88 .route("/search", web::get().to(search::<E>))
89 .route("/create", web::post().to(create_post::<E>))
90 .route("/edit/{id}", web::get().to(edit_get::<E>))
91 .route("/edit/{id}", web::post().to(edit_post::<E>))
92 .route("/delete", web::delete().to(delete_many::<E>))
93 .route("/delete/{id}", web::delete().to(delete::<E>))
94 .route("/show/{id}", web::get().to(show::<E>))
95 .route("/file/{id}/{column_name}", web::get().to(download::<E>))
96 .route(
97 "/file/{id}/{column_name}",
98 web::delete().to(delete_file::<E>),
99 )
100 .default_service(web::to(not_found)),
101 );
102
103 if let Err(e) = fs::create_dir_all(format!(
104 "{}/{}",
105 self.actix_admin.configuration.file_upload_directory,
106 E::get_entity_name()
107 )) {
108 log::warn!(
113 "actix_admin: could not create upload directory for entity `{}`: {e}",
114 E::get_entity_name()
115 );
116 }
117
118 let menu_element = ActixAdminMenuElement {
119 name: E::get_entity_name(),
120 link: E::get_entity_name(),
121 is_custom_handler: false,
122 };
123 self.push_menu_element(category_name, menu_element, false);
124
125 self.actix_admin
126 .view_models
127 .insert(E::get_entity_name(), view_model.clone());
128 }
129
130 pub fn add_custom_handler_for_index(&mut self, route: Route) {
131 self.custom_index = Some(route);
132 }
133
134 pub fn add_bulk_action_for_entity<
140 E: ActixAdminViewModelTrait + ActixAdminBulkActionDispatch + 'static,
141 >(
142 &mut self,
143 action: ActixAdminBulkAction,
144 ) {
145 let entity_name = E::get_entity_name();
146 let vm = self
147 .actix_admin
148 .view_models
149 .get_mut(&entity_name)
150 .unwrap_or_else(|| panic!("add_bulk_action_for_entity: entity `{entity_name}` must be registered via add_entity first"));
151 let is_first_action = vm.bulk_actions.is_empty();
152 vm.bulk_actions.push(action);
153
154 if is_first_action {
158 let scope = self
159 .scopes
160 .remove(&entity_name)
161 .unwrap_or_else(|| web::scope(&format!("/{}", entity_name)));
162 self.scopes.insert(
163 entity_name,
164 scope.route("/action/{name}", web::post().to(bulk_action::<E>)),
165 );
166 }
167 }
168
169 pub fn add_custom_handler_to_category(
170 &mut self,
171 menu_element_name: &str,
172 path: &str,
173 route: Route,
174 add_to_menu: bool,
175 category_name: &str,
176 ) {
177 self.custom_routes.push((path.to_string(), route));
178
179 if add_to_menu {
180 let menu_element = ActixAdminMenuElement {
181 name: menu_element_name.to_string(),
182 link: path.replacen("/", "", 1),
183 is_custom_handler: true,
184 };
185 self.push_menu_element(category_name, menu_element, true);
186 }
187 }
188
189 pub fn add_card_grid(
190 &mut self,
191 menu_element_name: &str,
192 path: &str,
193 elements: Vec<Vec<String>>,
194 add_to_menu: bool,
195 ) {
196 self.add_card_grid_to_category(menu_element_name, path, elements, add_to_menu, "");
197 }
198
199 pub fn add_card_grid_to_category(
200 &mut self,
201 menu_element_name: &str,
202 path: &str,
203 elements: Vec<Vec<String>>,
204 add_to_menu: bool,
205 category_name: &str,
206 ) {
207 self.custom_routes
208 .push((path.to_string(), web::get().to(display_card_grid)));
209 self.actix_admin
210 .card_grids
211 .insert(path.replace("/", ""), elements);
212
213 if add_to_menu {
214 let menu_element = ActixAdminMenuElement {
215 name: menu_element_name.to_string(),
216 link: path.replacen("/", "", 1),
217 is_custom_handler: true,
218 };
219 self.push_menu_element(category_name, menu_element, true);
220 }
221 }
222
223 pub fn add_custom_handler(
224 &mut self,
225 menu_element_name: &str,
226 path: &str,
227 route: Route,
228 add_to_menu: bool,
229 ) {
230 self.add_custom_handler_to_category(menu_element_name, path, route, add_to_menu, "");
231 }
232
233 pub fn add_support_handler(&mut self, arg: &str, support: Route) {
234 self.custom_routes.push((arg.to_string(), support));
235 self.actix_admin.support_path = Some(arg.replace("/", ""));
236 }
237
238 pub fn add_custom_handler_for_entity<E: ActixAdminViewModelTrait + 'static>(
239 &mut self,
240 menu_element_name: &str,
241 path: &str,
242 route: Route,
243 add_to_menu: bool,
244 ) {
245 self.add_custom_handler_for_entity_in_category::<E>(
246 menu_element_name,
247 path,
248 route,
249 "",
250 add_to_menu,
251 );
252 }
253
254 pub fn add_custom_handler_for_entity_in_category<E: ActixAdminViewModelTrait + 'static>(
255 &mut self,
256 menu_element_name: &str,
257 path: &str,
258 route: Route,
259 category_name: &str,
260 add_to_menu: bool,
261 ) {
262 let menu_element = ActixAdminMenuElement {
263 name: menu_element_name.to_string(),
264 link: format!("{}{}", E::get_entity_name(), path),
265 is_custom_handler: true,
266 };
267
268 let entity_name = E::get_entity_name();
269 let scope = self
270 .scopes
271 .remove(&entity_name)
272 .unwrap_or_else(|| web::scope(&format!("/{}", entity_name)));
273 self.scopes.insert(entity_name, scope.route(path, route));
274
275 if add_to_menu {
276 if let Some(entity_list) = self.actix_admin.entity_names.get_mut(category_name) {
277 if !entity_list.contains(&menu_element) {
278 entity_list.push(menu_element);
279 }
280 }
281 }
282 }
283
284 pub fn get_scope(self) -> actix_web::Scope {
285 let index_handler = self.custom_index.unwrap_or_else(|| web::get().to(index));
286 let mut admin_scope = web::scope(self.actix_admin.configuration.base_path)
287 .route("/", index_handler)
288 .default_service(web::to(not_found));
289
290 for (_, scope) in self.scopes {
291 admin_scope = admin_scope.service(scope);
292 }
293 for (path, route) in self.custom_routes {
294 admin_scope = admin_scope.route(&path, route);
295 }
296 admin_scope
297 }
298
299 pub fn get_actix_admin(&self) -> ActixAdmin {
300 self.actix_admin.clone()
301 }
302}
303
304impl ActixAdminBuilder {
305 fn push_menu_element(
308 &mut self,
309 category_name: &str,
310 element: ActixAdminMenuElement,
311 dedupe: bool,
312 ) {
313 let list = self
314 .actix_admin
315 .entity_names
316 .entry(category_name.to_string())
317 .or_default();
318 if !dedupe || !list.contains(&element) {
319 list.push(element);
320 }
321 }
322}