Skip to main content

reinhardt_admin/server/
delete.rs

1//! Delete operation Server Functions
2//!
3//! Provides delete operations for admin models (single and bulk).
4
5#[cfg(server)]
6use super::admin_auth::AdminAuthenticatedUser;
7use crate::adapters::{AdminDatabase, AdminRecord, AdminSite, BulkDeleteResponse};
8#[cfg(server)]
9use crate::core::{AdminDatabaseKey, AdminSiteKey};
10use crate::types::MutationResponse;
11#[cfg(server)]
12use reinhardt_di::Depends;
13#[cfg(server)]
14use reinhardt_pages::server_fn::ServerFnRequest;
15use reinhardt_pages::server_fn::{ServerFnError, server_fn};
16
17#[cfg(server)]
18use super::audit;
19#[cfg(server)]
20use super::error::{AdminAuth, MapServerFnError, ModelPermission};
21#[cfg(server)]
22use super::limits::MAX_BULK_DELETE_IDS;
23#[cfg(server)]
24use super::security::require_csrf_token;
25
26/// Delete a single model instance by ID
27///
28/// Removes a record from the database by its primary key.
29/// Returns the number of affected rows (typically 1) on success.
30///
31/// # Server Function
32///
33/// This function is automatically exposed as an HTTP endpoint by the `#[server_fn]` macro.
34/// AdminSite and AdminDatabase dependencies are automatically injected via the DI system.
35///
36/// # Authentication
37///
38/// Requires staff (admin) permission and delete permission for the model.
39///
40/// # Example
41///
42/// ```ignore
43/// use reinhardt_admin::server::delete_record;
44///
45/// // Client-side usage (automatically generates HTTP request)
46/// let response = delete_record("User".to_string(), "42".to_string(), "token".to_string()).await?;
47/// println!("Deleted: {}", response.message);
48/// ```
49#[server_fn]
50pub async fn delete_record(
51	model_name: String,
52	id: String,
53	csrf_token: String,
54	#[inject] site: Depends<AdminSiteKey, AdminSite>,
55	#[inject] db: Depends<AdminDatabaseKey, AdminDatabase>,
56	#[inject] http_request: ServerFnRequest,
57	#[inject] AdminAuthenticatedUser(user): AdminAuthenticatedUser,
58) -> Result<crate::types::MutationResponse, ServerFnError> {
59	// CSRF token validation (double-submit cookie pattern)
60	require_csrf_token(&csrf_token, &http_request.inner().headers)?;
61
62	// Authentication and authorization check
63	let auth = AdminAuth::from_request(&http_request);
64	let model_admin = site.get_model_admin(&model_name).map_server_fn_error()?;
65	auth.require_model_permission(model_admin.as_ref(), user.as_ref(), ModelPermission::Delete)
66		.await?;
67
68	let table_name = model_admin.table_name();
69	let pk_field = model_admin.pk_field();
70
71	let user_id = auth.user_id().unwrap_or("unknown").to_string();
72
73	let result = db
74		.delete::<AdminRecord>(table_name, pk_field, &id)
75		.await
76		.map_server_fn_error();
77
78	// Check for database errors first, logging failure before returning
79	let affected = match result {
80		Err(e) => {
81			audit::log_delete(&user_id, &model_name, &id, false);
82			return Err(e);
83		}
84		Ok(n) => n,
85	};
86
87	// Return 404 error when no record was found with the given ID.
88	// Only log success=true after confirming the record was actually deleted.
89	if affected == 0 {
90		audit::log_delete(&user_id, &model_name, &id, false);
91		return Err(ServerFnError::server(
92			404,
93			format!("{} not found", model_name),
94		));
95	}
96
97	audit::log_delete(&user_id, &model_name, &id, true);
98
99	Ok(MutationResponse {
100		success: true,
101		message: format!("{} deleted successfully", model_name),
102		affected: Some(affected),
103		data: None,
104	})
105}
106
107/// Delete multiple model instances by IDs (bulk delete)
108///
109/// Removes multiple records from the database using their primary keys.
110/// Returns the total number of deleted rows.
111///
112/// # Server Function
113///
114/// This function is automatically exposed as an HTTP endpoint by the `#[server_fn]` macro.
115/// AdminSite and AdminDatabase dependencies are automatically injected via the DI system.
116///
117/// # Authentication
118///
119/// Requires staff (admin) permission and delete permission for the model.
120///
121/// # Example
122///
123/// ```ignore
124/// use reinhardt_admin::server::bulk_delete_records;
125/// use reinhardt_admin::types::BulkDeleteRequest;
126///
127/// // Client-side usage (automatically generates HTTP request)
128/// let request = BulkDeleteRequest {
129///     csrf_token: "token".to_string(),
130///     ids: vec!["1".to_string(), "2".to_string(), "3".to_string()],
131/// };
132/// let response = bulk_delete_records("User".to_string(), request).await?;
133/// println!("Deleted {} items", response.deleted);
134/// ```
135#[server_fn]
136pub async fn bulk_delete_records(
137	model_name: String,
138	request: crate::adapters::BulkDeleteRequest,
139	#[inject] site: Depends<AdminSiteKey, AdminSite>,
140	#[inject] db: Depends<AdminDatabaseKey, AdminDatabase>,
141	#[inject] http_request: ServerFnRequest,
142	#[inject] AdminAuthenticatedUser(user): AdminAuthenticatedUser,
143) -> Result<crate::adapters::BulkDeleteResponse, ServerFnError> {
144	// CSRF token validation (double-submit cookie pattern)
145	require_csrf_token(&request.csrf_token, &http_request.inner().headers)?;
146
147	// Authentication and authorization check
148	let auth = AdminAuth::from_request(&http_request);
149	let model_admin = site.get_model_admin(&model_name).map_server_fn_error()?;
150	auth.require_model_permission(model_admin.as_ref(), user.as_ref(), ModelPermission::Delete)
151		.await?;
152
153	let table_name = model_admin.table_name();
154	let pk_field = model_admin.pk_field();
155
156	let user_id = auth.user_id().unwrap_or("unknown").to_string();
157
158	let ids = request.ids;
159	if ids.len() > MAX_BULK_DELETE_IDS {
160		return Err(ServerFnError::application(format!(
161			"Too many IDs for bulk delete: {} exceeds maximum of {}",
162			ids.len(),
163			MAX_BULK_DELETE_IDS
164		)));
165	}
166
167	let result = db
168		.bulk_delete::<AdminRecord>(table_name, pk_field, ids.clone())
169		.await
170		.map_server_fn_error();
171
172	let success = result.is_ok();
173	let affected_count = result.as_ref().copied().unwrap_or(0);
174	audit::log_bulk_delete(&user_id, &model_name, &ids, affected_count, success);
175
176	let affected = result?;
177
178	Ok(BulkDeleteResponse {
179		success: affected > 0,
180		deleted: affected,
181		message: format!("Deleted {} {} items", affected, model_name),
182	})
183}