reinhardt_admin/server/
create.rs1#[cfg(server)]
6use super::admin_auth::AdminAuthenticatedUser;
7use crate::adapters::{AdminDatabase, AdminRecord, AdminSite};
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::security::{require_csrf_token, sanitize_mutation_values};
23#[cfg(server)]
24use super::validation::validate_mutation_data;
25
26#[server_fn]
57pub async fn create_record(
58 model_name: String,
59 request: crate::types::MutationRequest,
60 #[inject] site: Depends<AdminSiteKey, AdminSite>,
61 #[inject] db: Depends<AdminDatabaseKey, AdminDatabase>,
62 #[inject] http_request: ServerFnRequest,
63 #[inject] AdminAuthenticatedUser(user): AdminAuthenticatedUser,
64) -> Result<crate::types::MutationResponse, ServerFnError> {
65 require_csrf_token(&request.csrf_token, &http_request.inner().headers)?;
67
68 let auth = AdminAuth::from_request(&http_request);
70 let model_admin = site.get_model_admin(&model_name).map_server_fn_error()?;
71 auth.require_model_permission(model_admin.as_ref(), user.as_ref(), ModelPermission::Add)
72 .await?;
73 let table_name = model_admin.table_name();
74 let pk_field = model_admin.pk_field();
75
76 validate_mutation_data(&request.data, model_admin.as_ref(), false).map_server_fn_error()?;
78
79 let mut sanitized_data = request.data;
81 sanitize_mutation_values(&mut sanitized_data);
82
83 inject_auto_timestamps(&mut sanitized_data, table_name);
88
89 let user_id = auth.user_id().unwrap_or("unknown").to_string();
90
91 let result = db
92 .create::<AdminRecord>(table_name, Some(pk_field), sanitized_data.clone())
93 .await
94 .map_server_fn_error();
95
96 let success = result.is_ok();
97 audit::log_create(&user_id, &model_name, &sanitized_data, success);
98
99 let affected = result?;
100
101 Ok(MutationResponse {
102 success: true,
103 message: format!("{} created successfully", model_name),
104 affected: Some(affected),
105 data: None,
106 })
107}
108
109#[cfg(server)]
118pub(crate) fn inject_auto_timestamps(
119 data: &mut std::collections::HashMap<String, serde_json::Value>,
120 table_name: &str,
121) {
122 use crate::server::type_inference::find_model_by_table_name;
123
124 let Some(model) = find_model_by_table_name(table_name) else {
125 return;
126 };
127
128 let now = chrono::Utc::now();
129
130 for (field_name, meta) in &model.fields {
131 let is_auto_now = meta
132 .params
133 .get("auto_now")
134 .is_some_and(|v| v == "true" || v == "True");
135 let is_auto_now_add = meta
136 .params
137 .get("auto_now_add")
138 .is_some_and(|v| v == "true" || v == "True");
139
140 if is_auto_now || is_auto_now_add {
141 let value = match &meta.field_type {
143 reinhardt_db::migrations::FieldType::Date => {
144 serde_json::Value::String(now.format("%Y-%m-%d").to_string())
145 }
146 reinhardt_db::migrations::FieldType::Time => {
147 serde_json::Value::String(now.format("%H:%M:%S").to_string())
148 }
149 _ => {
150 serde_json::Value::String(now.format("%Y-%m-%dT%H:%M:%S%.6fZ").to_string())
152 }
153 };
154 data.insert(field_name.clone(), value);
155 }
156 }
157}
158
159#[cfg(server)]
164pub(crate) fn inject_auto_now_timestamps(
165 data: &mut std::collections::HashMap<String, serde_json::Value>,
166 table_name: &str,
167) {
168 use crate::server::type_inference::find_model_by_table_name;
169
170 let Some(model) = find_model_by_table_name(table_name) else {
171 return;
172 };
173
174 let now = chrono::Utc::now();
175
176 for (field_name, meta) in &model.fields {
177 let is_auto_now = meta
178 .params
179 .get("auto_now")
180 .is_some_and(|v| v == "true" || v == "True");
181
182 if is_auto_now {
183 let value = match &meta.field_type {
184 reinhardt_db::migrations::FieldType::Date => {
185 serde_json::Value::String(now.format("%Y-%m-%d").to_string())
186 }
187 reinhardt_db::migrations::FieldType::Time => {
188 serde_json::Value::String(now.format("%H:%M:%S").to_string())
189 }
190 _ => serde_json::Value::String(now.format("%Y-%m-%dT%H:%M:%S%.6fZ").to_string()),
191 };
192 data.insert(field_name.clone(), value);
193 }
194 }
195}