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.
This commit is contained in:
Mahmoud-Emad 2025-06-03 15:12:53 +03:00
parent 2299b61e79
commit 9802d51acc
8 changed files with 47 additions and 111 deletions

View File

@ -8,8 +8,9 @@ use tera::Tera;
use crate::db::calendar::{ use crate::db::calendar::{
add_event_to_calendar, create_new_event, delete_event, get_events, get_or_create_user_calendar, add_event_to_calendar, create_new_event, delete_event, get_events, get_or_create_user_calendar,
}; };
use crate::models::{CalendarEvent, CalendarViewMode}; use crate::models::CalendarViewMode;
use crate::utils::render_template; use crate::utils::render_template;
use heromodels::models::calendar::Event;
use heromodels_core::Model; use heromodels_core::Model;
/// Controller for handling calendar-related routes /// Controller for handling calendar-related routes
@ -138,23 +139,13 @@ impl CalendarController {
// Get events from database // Get events from database
let events = match get_events() { let events = match get_events() {
Ok(db_events) => { Ok(db_events) => {
// Filter events for the date range and convert to CalendarEvent format // Filter events for the date range
db_events db_events
.into_iter() .into_iter()
.filter(|event| { .filter(|event| {
// Event overlaps with the date range // Event overlaps with the date range
event.start_time < end_date && event.end_time > start_date event.start_time < end_date && event.end_time > start_date
}) })
.map(|event| CalendarEvent {
id: event.get_id().to_string(),
title: event.title.clone(),
description: event.description.clone().unwrap_or_default(),
start_time: event.start_time,
end_time: event.end_time,
color: event.color.clone().unwrap_or_else(|| "#4285F4".to_string()),
all_day: event.all_day,
user_id: event.created_by.map(|id| id.to_string()),
})
.collect() .collect()
} }
Err(e) => { Err(e) => {
@ -574,7 +565,7 @@ pub struct EventForm {
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
struct CalendarDay { struct CalendarDay {
day: u32, day: u32,
events: Vec<CalendarEvent>, events: Vec<Event>,
is_current_month: bool, is_current_month: bool,
} }
@ -583,5 +574,5 @@ struct CalendarDay {
struct CalendarMonth { struct CalendarMonth {
month: u32, month: u32,
name: String, name: String,
events: Vec<CalendarEvent>, events: Vec<Event>,
} }

View File

@ -1,7 +1,7 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use heromodels::{ use heromodels::{
db::{Collection, Db}, db::{Collection, Db},
models::calendar::{AttendanceStatus, Attendee, Calendar, Event, EventStatus}, models::calendar::{AttendanceStatus, Attendee, Calendar, Event},
}; };
use super::db::get_db; use super::db::get_db;

View File

@ -1,3 +1,4 @@
pub mod calendar; pub mod calendar;
pub mod contracts;
pub mod db; pub mod db;
pub mod governance; pub mod governance;

View File

@ -1,61 +1,4 @@
use chrono::{DateTime, Utc}; // No imports needed for this module currently
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// Represents a calendar event
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalendarEvent {
/// Unique identifier for the event
pub id: String,
/// Title of the event
pub title: String,
/// Description of the event
pub description: String,
/// Start time of the event
pub start_time: DateTime<Utc>,
/// End time of the event
pub end_time: DateTime<Utc>,
/// Color of the event (hex code)
pub color: String,
/// Whether the event is an all-day event
pub all_day: bool,
/// User ID of the event creator
pub user_id: Option<String>,
}
impl CalendarEvent {
/// Creates a new calendar event
pub fn new(
title: String,
description: String,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
color: Option<String>,
all_day: bool,
user_id: Option<String>,
) -> Self {
Self {
id: Uuid::new_v4().to_string(),
title,
description,
start_time,
end_time,
color: color.unwrap_or_else(|| "#4285F4".to_string()), // Google Calendar blue
all_day,
user_id,
}
}
/// Converts the event to a JSON string
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
/// Creates an event from a JSON string
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
}
/// Represents a view mode for the calendar /// Represents a view mode for the calendar
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -91,4 +34,4 @@ impl CalendarViewMode {
Self::Day => "day", Self::Day => "day",
} }
} }
} }

View File

@ -10,7 +10,7 @@ pub mod ticket;
pub mod user; pub mod user;
// Re-export models for easier imports // Re-export models for easier imports
pub use calendar::{CalendarEvent, CalendarViewMode}; pub use calendar::CalendarViewMode;
pub use defi::initialize_mock_data; pub use defi::initialize_mock_data;
pub use ticket::{Ticket, TicketComment, TicketPriority, TicketStatus}; pub use ticket::{Ticket, TicketComment, TicketPriority, TicketStatus};
pub use user::User; pub use user::User;

View File

@ -7,7 +7,7 @@ use tera::{self, Context, Function, Tera, Value};
pub mod redis_service; pub mod redis_service;
// Re-export for easier imports // Re-export for easier imports
pub use redis_service::RedisCalendarService; // pub use redis_service::RedisCalendarService; // Currently unused
/// Error type for template rendering /// Error type for template rendering
#[derive(Debug)] #[derive(Debug)]

View File

@ -1,7 +1,7 @@
use heromodels::models::Event as CalendarEvent;
use lazy_static::lazy_static;
use redis::{Client, Commands, Connection, RedisError}; use redis::{Client, Commands, Connection, RedisError};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use lazy_static::lazy_static;
use crate::models::CalendarEvent;
// Create a lazy static Redis client that can be used throughout the application // Create a lazy static Redis client that can be used throughout the application
lazy_static! { lazy_static! {
@ -11,21 +11,21 @@ lazy_static! {
/// Initialize the Redis client /// Initialize the Redis client
pub fn init_redis_client(redis_url: &str) -> Result<(), RedisError> { pub fn init_redis_client(redis_url: &str) -> Result<(), RedisError> {
let client = redis::Client::open(redis_url)?; let client = redis::Client::open(redis_url)?;
// Test the connection // Test the connection
let _: Connection = client.get_connection()?; let _: Connection = client.get_connection()?;
// Store the client in the lazy static // Store the client in the lazy static
let mut client_guard = REDIS_CLIENT.lock().unwrap(); let mut client_guard = REDIS_CLIENT.lock().unwrap();
*client_guard = Some(client); *client_guard = Some(client);
Ok(()) Ok(())
} }
/// Get a Redis connection /// Get a Redis connection
pub fn get_connection() -> Result<Connection, RedisError> { pub fn get_connection() -> Result<Connection, RedisError> {
let client_guard = REDIS_CLIENT.lock().unwrap(); let client_guard = REDIS_CLIENT.lock().unwrap();
if let Some(client) = &*client_guard { if let Some(client) = &*client_guard {
client.get_connection() client.get_connection()
} else { } else {
@ -42,14 +42,14 @@ pub struct RedisCalendarService;
impl RedisCalendarService { impl RedisCalendarService {
/// Key prefix for calendar events /// Key prefix for calendar events
const EVENT_KEY_PREFIX: &'static str = "calendar:event:"; const EVENT_KEY_PREFIX: &'static str = "calendar:event:";
/// Key for the set of all event IDs /// Key for the set of all event IDs
const ALL_EVENTS_KEY: &'static str = "calendar:all_events"; const ALL_EVENTS_KEY: &'static str = "calendar:all_events";
/// Save a calendar event to Redis /// Save a calendar event to Redis
pub fn save_event(event: &CalendarEvent) -> Result<(), RedisError> { pub fn save_event(event: &CalendarEvent) -> Result<(), RedisError> {
let mut conn = get_connection()?; let mut conn = get_connection()?;
// Convert the event to JSON // Convert the event to JSON
let json = event.to_json().map_err(|e| { let json = event.to_json().map_err(|e| {
RedisError::from(std::io::Error::new( RedisError::from(std::io::Error::new(
@ -57,25 +57,25 @@ impl RedisCalendarService {
format!("Failed to serialize event: {}", e), format!("Failed to serialize event: {}", e),
)) ))
})?; })?;
// Save the event // Save the event
let event_key = format!("{}{}", Self::EVENT_KEY_PREFIX, event.id); let event_key = format!("{}{}", Self::EVENT_KEY_PREFIX, event.base_data.id);
let _: () = conn.set(event_key, json)?; let _: () = conn.set(event_key, json)?;
// Add the event ID to the set of all events // Add the event ID to the set of all events
let _: () = conn.sadd(Self::ALL_EVENTS_KEY, &event.id)?; let _: () = conn.sadd(Self::ALL_EVENTS_KEY, &event.base_data.id)?;
Ok(()) Ok(())
} }
/// Get a calendar event from Redis by ID /// Get a calendar event from Redis by ID
pub fn get_event(id: &str) -> Result<Option<CalendarEvent>, RedisError> { pub fn get_event(id: &str) -> Result<Option<CalendarEvent>, RedisError> {
let mut conn = get_connection()?; let mut conn = get_connection()?;
// Get the event JSON // Get the event JSON
let event_key = format!("{}{}", Self::EVENT_KEY_PREFIX, id); let event_key = format!("{}{}", Self::EVENT_KEY_PREFIX, id);
let json: Option<String> = conn.get(event_key)?; let json: Option<String> = conn.get(event_key)?;
// Parse the JSON // Parse the JSON
if let Some(json) = json { if let Some(json) = json {
let event = CalendarEvent::from_json(&json).map_err(|e| { let event = CalendarEvent::from_json(&json).map_err(|e| {
@ -84,34 +84,34 @@ impl RedisCalendarService {
format!("Failed to deserialize event: {}", e), format!("Failed to deserialize event: {}", e),
)) ))
})?; })?;
Ok(Some(event)) Ok(Some(event))
} else { } else {
Ok(None) Ok(None)
} }
} }
/// Delete a calendar event from Redis /// Delete a calendar event from Redis
pub fn delete_event(id: &str) -> Result<bool, RedisError> { pub fn delete_event(id: &str) -> Result<bool, RedisError> {
let mut conn = get_connection()?; let mut conn = get_connection()?;
// Delete the event // Delete the event
let event_key = format!("{}{}", Self::EVENT_KEY_PREFIX, id); let event_key = format!("{}{}", Self::EVENT_KEY_PREFIX, id);
let deleted: i32 = conn.del(event_key)?; let deleted: i32 = conn.del(event_key)?;
// Remove the event ID from the set of all events // Remove the event ID from the set of all events
let _: () = conn.srem(Self::ALL_EVENTS_KEY, id)?; let _: () = conn.srem(Self::ALL_EVENTS_KEY, id)?;
Ok(deleted > 0) Ok(deleted > 0)
} }
/// Get all calendar events from Redis /// Get all calendar events from Redis
pub fn get_all_events() -> Result<Vec<CalendarEvent>, RedisError> { pub fn get_all_events() -> Result<Vec<CalendarEvent>, RedisError> {
let mut conn = get_connection()?; let mut conn = get_connection()?;
// Get all event IDs // Get all event IDs
let event_ids: Vec<String> = conn.smembers(Self::ALL_EVENTS_KEY)?; let event_ids: Vec<String> = conn.smembers(Self::ALL_EVENTS_KEY)?;
// Get all events // Get all events
let mut events = Vec::new(); let mut events = Vec::new();
for id in event_ids { for id in event_ids {
@ -119,23 +119,23 @@ impl RedisCalendarService {
events.push(event); events.push(event);
} }
} }
Ok(events) Ok(events)
} }
/// Get events for a specific date range /// Get events for a specific date range
pub fn get_events_in_range( pub fn get_events_in_range(
start: chrono::DateTime<chrono::Utc>, start: chrono::DateTime<chrono::Utc>,
end: chrono::DateTime<chrono::Utc>, end: chrono::DateTime<chrono::Utc>,
) -> Result<Vec<CalendarEvent>, RedisError> { ) -> Result<Vec<CalendarEvent>, RedisError> {
let all_events = Self::get_all_events()?; let all_events = Self::get_all_events()?;
// Filter events that fall within the date range // Filter events that fall within the date range
let filtered_events = all_events let filtered_events = all_events
.into_iter() .into_iter()
.filter(|event| event.start_time <= end && event.end_time >= start) .filter(|event| event.start_time <= end && event.end_time >= start)
.collect(); .collect();
Ok(filtered_events) Ok(filtered_events)
} }
} }

View File

@ -114,8 +114,9 @@
{% for event in day.events %} {% for event in day.events %}
<div class="event-preview text-truncate mb-1 {% if loop.index > 2 %}d-none{% endif %}" <div class="event-preview text-truncate mb-1 {% if loop.index > 2 %}d-none{% endif %}"
style="background-color: {{ event.color }}; color: white;" style="background-color: {{ event.color }}; color: white;"
onclick="openEventDetails(event, '{{ event.id }}', '{{ event.title|escape }}', '{{ event.description|escape }}', '{{ event.color }}', {{ event.all_day }}, '{{ event.start_time }}', '{{ event.end_time }}')" onclick="openEventDetails(event, '{{ event.base_data.id }}', '{{ event.title|escape }}', '{{ event.description|escape }}', '{{ event.color }}', {{ event.all_day }}, '{{ event.start_time }}', '{{ event.end_time }}')"
data-event-id="{{ event.id }}" data-event-title="{{ event.title|escape }}" data-event-id="{{ event.base_data.id }}"
data-event-title="{{ event.title|escape }}"
data-event-description="{{ event.description|escape }}" data-event-description="{{ event.description|escape }}"
data-event-color="{{ event.color }}" data-event-color="{{ event.color }}"
data-event-all-day="{{ event.all_day }}" data-event-all-day="{{ event.all_day }}"
@ -204,7 +205,7 @@
{% if event.all_day %} {% if event.all_day %}
{% set_global has_all_day_events = true %} {% set_global has_all_day_events = true %}
<div class="alert mb-2" style="background-color: {{ event.color }}; color: white; cursor: pointer;" <div class="alert mb-2" style="background-color: {{ event.color }}; color: white; cursor: pointer;"
onclick="openEventDetails(event, '{{ event.id }}', '{{ event.title|escape }}', '{{ event.description|escape }}', '{{ event.color }}', {{ event.all_day }}, '{{ event.start_time }}', '{{ event.end_time }}')"> onclick="openEventDetails(event, '{{ event.base_data.id }}', '{{ event.title|escape }}', '{{ event.description|escape }}', '{{ event.color }}', {{ event.all_day }}, '{{ event.start_time }}', '{{ event.end_time }}')">
<h5 class="mb-1">{{ event.title }}</h5> <h5 class="mb-1">{{ event.title }}</h5>
{% if event.description %} {% if event.description %}
<p class="mb-0">{{ event.description }}</p> <p class="mb-0">{{ event.description }}</p>
@ -235,7 +236,7 @@
{% set start_hour = event.start_time|extract_hour %} {% set start_hour = event.start_time|extract_hour %}
{% if start_hour == hour %} {% if start_hour == hour %}
<div class="alert mb-2" style="background-color: {{ event.color }}; color: white; cursor: pointer;" <div class="alert mb-2" style="background-color: {{ event.color }}; color: white; cursor: pointer;"
onclick="openEventDetails(event, '{{ event.id }}', '{{ event.title|escape }}', '{{ event.description|escape }}', '{{ event.color }}', {{ event.all_day }}, '{{ event.start_time }}', '{{ event.end_time }}')"> onclick="openEventDetails(event, '{{ event.base_data.id }}', '{{ event.title|escape }}', '{{ event.description|escape }}', '{{ event.color }}', {{ event.all_day }}, '{{ event.start_time }}', '{{ event.end_time }}')">
<h5>{{ event.title }}</h5> <h5>{{ event.title }}</h5>
<p>{{ event.start_time|format_time }} - {{ event.end_time|format_time }}</p> <p>{{ event.start_time|format_time }} - {{ event.end_time|format_time }}</p>
<p>{{ event.description }}</p> <p>{{ event.description }}</p>
@ -859,7 +860,7 @@
eventDiv.onclick = (e) => { eventDiv.onclick = (e) => {
e.stopPropagation(); e.stopPropagation();
closeEventsPopup(); closeEventsPopup();
openEventDetails(e, event.id, event.title, event.description, event.color, event.all_day, event.start_time, event.end_time); openEventDetails(e, event.base_data.id, event.title, event.description, event.color, event.all_day, event.start_time, event.end_time);
}; };
content.appendChild(eventDiv); content.appendChild(eventDiv);
}); });