hostbasket/actix_mvc_app/src/utils/mod.rs
Mahmoud-Emad 9802d51acc feat: Migrate to hero-models for calendar event data
- Replaced custom `CalendarEvent` model with `heromodels`' `Event` model.
- Updated database interactions and controller logic to use the new model.
- Removed unnecessary `CalendarEvent` model and related code.
- Updated views to reflect changes in event data structure.
2025-06-03 15:12:53 +03:00

324 lines
11 KiB
Rust

use actix_web::{Error, HttpResponse};
use chrono::{DateTime, Utc};
use std::error::Error as StdError;
use tera::{self, Context, Function, Tera, Value};
// Export modules
pub mod redis_service;
// Re-export for easier imports
// pub use redis_service::RedisCalendarService; // Currently unused
/// Error type for template rendering
#[derive(Debug)]
#[allow(dead_code)]
pub struct TemplateError {
pub message: String,
pub details: String,
pub location: String,
}
impl std::fmt::Display for TemplateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Template error in {}: {}", self.location, self.message)
}
}
impl std::error::Error for TemplateError {}
/// Registers custom Tera functions and filters
pub fn register_tera_functions(tera: &mut tera::Tera) {
tera.register_function("now", NowFunction);
tera.register_function("format_date", FormatDateFunction);
tera.register_function("local_time", LocalTimeFunction);
// Register custom filters
tera.register_filter("format_hour", format_hour_filter);
tera.register_filter("extract_hour", extract_hour_filter);
tera.register_filter("format_time", format_time_filter);
}
/// Tera function to get the current date/time
#[derive(Clone)]
pub struct NowFunction;
impl Function for NowFunction {
fn call(&self, args: &std::collections::HashMap<String, Value>) -> tera::Result<Value> {
let format = match args.get("format") {
Some(val) => match val.as_str() {
Some(s) => s,
None => "%Y-%m-%d %H:%M:%S",
},
None => "%Y-%m-%d %H:%M:%S",
};
let now = Utc::now();
// Special case for just getting the year
if args.get("year").and_then(|v| v.as_bool()).unwrap_or(false) {
return Ok(Value::String(now.format("%Y").to_string()));
}
Ok(Value::String(now.format(format).to_string()))
}
}
/// Tera function to format a date
#[derive(Clone)]
pub struct FormatDateFunction;
impl Function for FormatDateFunction {
fn call(&self, args: &std::collections::HashMap<String, Value>) -> tera::Result<Value> {
let timestamp = match args.get("timestamp") {
Some(val) => match val.as_i64() {
Some(ts) => ts,
None => {
return Err(tera::Error::msg(
"The 'timestamp' argument must be a valid timestamp",
));
}
},
None => return Err(tera::Error::msg("The 'timestamp' argument is required")),
};
let format = match args.get("format") {
Some(val) => match val.as_str() {
Some(s) => s,
None => "%Y-%m-%d %H:%M:%S",
},
None => "%Y-%m-%d %H:%M:%S",
};
// Convert timestamp to DateTime using the non-deprecated method
let datetime = match DateTime::from_timestamp(timestamp, 0) {
Some(dt) => dt,
None => return Err(tera::Error::msg("Failed to convert timestamp to datetime")),
};
Ok(Value::String(datetime.format(format).to_string()))
}
}
/// Tera function to convert UTC datetime to local time
#[derive(Clone)]
pub struct LocalTimeFunction;
impl Function for LocalTimeFunction {
fn call(&self, args: &std::collections::HashMap<String, Value>) -> tera::Result<Value> {
let datetime_value = match args.get("datetime") {
Some(val) => val,
None => return Err(tera::Error::msg("The 'datetime' argument is required")),
};
let format = match args.get("format") {
Some(val) => match val.as_str() {
Some(s) => s,
None => "%Y-%m-%d %H:%M",
},
None => "%Y-%m-%d %H:%M",
};
// The datetime comes from Rust as a serialized DateTime<Utc>
// We need to handle it properly
let utc_datetime = if let Some(dt_str) = datetime_value.as_str() {
// Try to parse as RFC3339 first
match DateTime::parse_from_rfc3339(dt_str) {
Ok(dt) => dt.with_timezone(&Utc),
Err(_) => {
// Try to parse as our standard format
match DateTime::parse_from_str(dt_str, "%Y-%m-%d %H:%M:%S%.f UTC") {
Ok(dt) => dt.with_timezone(&Utc),
Err(_) => return Err(tera::Error::msg("Invalid datetime string format")),
}
}
}
} else {
return Err(tera::Error::msg("Datetime must be a string"));
};
// Convert UTC to local time (EEST = UTC+3)
// In a real application, you'd want to get the user's timezone from their profile
let local_offset = chrono::FixedOffset::east_opt(3 * 3600).unwrap(); // +3 hours for EEST
let local_datetime = utc_datetime.with_timezone(&local_offset);
Ok(Value::String(local_datetime.format(format).to_string()))
}
}
/// Tera filter to format hour with zero padding
pub fn format_hour_filter(
value: &Value,
_args: &std::collections::HashMap<String, Value>,
) -> tera::Result<Value> {
match value.as_i64() {
Some(hour) => Ok(Value::String(format!("{:02}", hour))),
None => Err(tera::Error::msg("Value must be a number")),
}
}
/// Tera filter to extract hour from datetime string
pub fn extract_hour_filter(
value: &Value,
_args: &std::collections::HashMap<String, Value>,
) -> tera::Result<Value> {
match value.as_str() {
Some(datetime_str) => {
// Try to parse as RFC3339 first
if let Ok(dt) = DateTime::parse_from_rfc3339(datetime_str) {
Ok(Value::String(dt.format("%H").to_string()))
} else {
// Try to parse as our standard format
match DateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S%.f UTC") {
Ok(dt) => Ok(Value::String(dt.format("%H").to_string())),
Err(_) => Err(tera::Error::msg("Invalid datetime string format")),
}
}
}
None => Err(tera::Error::msg("Value must be a string")),
}
}
/// Tera filter to format time from datetime string
pub fn format_time_filter(
value: &Value,
args: &std::collections::HashMap<String, Value>,
) -> tera::Result<Value> {
let format = match args.get("format") {
Some(val) => match val.as_str() {
Some(s) => s,
None => "%H:%M",
},
None => "%H:%M",
};
match value.as_str() {
Some(datetime_str) => {
// Try to parse as RFC3339 first
if let Ok(dt) = DateTime::parse_from_rfc3339(datetime_str) {
Ok(Value::String(dt.format(format).to_string()))
} else {
// Try to parse as our standard format
match DateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S%.f UTC") {
Ok(dt) => Ok(Value::String(dt.format(format).to_string())),
Err(_) => Err(tera::Error::msg("Invalid datetime string format")),
}
}
}
None => Err(tera::Error::msg("Value must be a string")),
}
}
/// Formats a date for display
#[allow(dead_code)]
pub fn format_date(date: &DateTime<Utc>, format: &str) -> String {
date.format(format).to_string()
}
/// Truncates a string to a maximum length and adds an ellipsis if truncated
#[allow(dead_code)]
pub fn truncate_string(s: &str, max_length: usize) -> String {
if s.len() <= max_length {
s.to_string()
} else {
format!("{}...", &s[0..max_length])
}
}
/// Renders a template with error handling
///
/// This function attempts to render a template and handles any errors by rendering
/// the error template with detailed error information.
pub fn render_template(
tmpl: &Tera,
template_name: &str,
ctx: &Context,
) -> Result<HttpResponse, Error> {
println!("DEBUG: Attempting to render template: {}", template_name);
// Print all context keys for debugging
let mut keys = Vec::new();
for (key, _) in ctx.clone().into_json().as_object().unwrap().iter() {
keys.push(key.clone());
}
println!("DEBUG: Context keys: {:?}", keys);
match tmpl.render(template_name, ctx) {
Ok(content) => {
println!("DEBUG: Successfully rendered template: {}", template_name);
Ok(HttpResponse::Ok().content_type("text/html").body(content))
}
Err(e) => {
// Log the error with more details
println!(
"DEBUG: Template rendering error for {}: {}",
template_name, e
);
println!("DEBUG: Error details: {:?}", e);
// Print the error cause chain for better debugging
let mut current_error: Option<&dyn StdError> = Some(&e);
let mut error_chain = Vec::new();
while let Some(error) = current_error {
error_chain.push(format!("{}", error));
current_error = error.source();
}
println!("DEBUG: Error chain: {:?}", error_chain);
// Log the error
log::error!("Template rendering error: {}", e);
// Create a simple error response with more detailed information
let error_html = format!(
r#"<!DOCTYPE html>
<html>
<head>
<title>Template Error</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }}
.error-container {{ border: 1px solid #f5c6cb; background-color: #f8d7da; padding: 20px; border-radius: 5px; }}
.error-title {{ color: #721c24; }}
.error-details {{ background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin-top: 20px; }}
pre {{ background-color: #f1f1f1; padding: 10px; overflow: auto; }}
</style>
</head>
<body>
<div class="error-container">
<h1 class="error-title">Template Rendering Error</h1>
<p>There was an error rendering the template: <strong>{}</strong></p>
<div class="error-details">
<h3>Error Details:</h3>
<pre>{}</pre>
<h3>Error Chain:</h3>
<pre>{}</pre>
</div>
</div>
</body>
</html>"#,
template_name,
e,
error_chain.join("\n")
);
println!("DEBUG: Returning simple error page");
Ok(HttpResponse::InternalServerError()
.content_type("text/html")
.body(error_html))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_truncate_string() {
assert_eq!(truncate_string("Hello", 10), "Hello");
assert_eq!(truncate_string("Hello, world!", 5), "Hello...");
assert_eq!(truncate_string("", 5), "");
}
}