1use crate::auth::UserStore;
2use crate::handlers;
3use crate::registry::{AdminRegistry, SharedRegistry};
4use crate::resource::{AdminPrefix, AdminTitle};
5use actix_web::web;
6use std::sync::Arc;
7
8pub struct AdminSite {
10 prefix: String,
11 title: String,
12 user_store: Option<Arc<dyn UserStore>>,
13}
14
15impl AdminSite {
16 pub fn new(prefix: &str) -> Self {
18 let prefix = prefix.trim_start_matches('/');
19 Self {
20 prefix: format!("/{}", prefix),
21 title: "Admin".to_string(),
22 user_store: None,
23 }
24 }
25
26 pub fn title(mut self, t: &str) -> Self {
28 self.title = t.to_string();
29 self
30 }
31
32 pub fn with_user_store(mut self, store: Arc<dyn UserStore>) -> Self {
34 self.user_store = Some(store);
35 self
36 }
37
38 pub fn mount(self, cfg: &mut web::ServiceConfig, registry: AdminRegistry) {
40 let shared_registry: SharedRegistry = Arc::new(registry);
41 let prefix = self.prefix.clone();
42 let title = self.title.clone();
43
44 cfg.app_data(web::Data::new(shared_registry.clone()));
45 cfg.app_data(web::Data::new(AdminTitle(title)));
46 cfg.app_data(web::Data::new(AdminPrefix(prefix.trim_start_matches('/').to_string())));
47
48 if let Some(ref store) = self.user_store {
49 cfg.app_data(web::Data::new(store.clone()));
50 } else {
51 log::warn!(
53 "No UserStore configured for admin site at {}. \
54 Login will reject all attempts. \
55 Use AdminSite::with_user_store() to provide one.",
56 prefix
57 );
58 let noop: Arc<dyn UserStore> = Arc::new(NoopUserStore);
59 cfg.app_data(web::Data::new(noop));
60 }
61
62 cfg.service(
63 web::scope(&prefix)
64 .wrap(crate::auth::RequireAuth::new(&prefix))
65 .route("", web::get().to(handlers::dashboard::index))
66 .route("/", web::get().to(handlers::dashboard::index))
67 .route("/login", web::get().to(handlers::auth::login_page))
68 .route("/login", web::post().to(handlers::auth::login))
69 .route("/logout", web::get().to(handlers::auth::logout))
70 .service(
71 web::scope("/{slug}")
72 .route("", web::get().to(handlers::resource::list))
73 .route("/", web::get().to(handlers::resource::list))
74 .route("/new", web::get().to(handlers::resource::new))
75 .route("/new", web::post().to(handlers::resource::create))
76 .route("/{id}", web::get().to(handlers::resource::edit))
77 .route("/{id}", web::post().to(handlers::resource::update))
78 .route("/{id}/delete", web::post().to(handlers::resource::delete)),
79 ),
80 );
81 }
82}
83
84struct NoopUserStore;
85
86#[async_trait::async_trait]
87impl UserStore for NoopUserStore {
88 async fn find_by_username(&self, _: &str) -> Result<Option<crate::auth::User>, crate::auth::AuthError> {
89 Ok(None)
90 }
91
92 async fn create_user(
93 &self,
94 _: &str,
95 _: &str,
96 _: &str,
97 _: &str,
98 _: bool,
99 ) -> Result<crate::auth::User, crate::auth::AuthError> {
100 Err(crate::auth::AuthError::Storage("NoopUserStore: cannot create users".to_string()))
101 }
102
103 async fn delete_user(&self, _: &str) -> Result<(), crate::auth::AuthError> {
104 Err(crate::auth::AuthError::Storage("NoopUserStore: cannot delete users".to_string()))
105 }
106}