...
This commit is contained in:
		
							
								
								
									
										80
									
								
								actix_mvc_app/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										80
									
								
								actix_mvc_app/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -144,7 +144,7 @@ dependencies = [
 | 
			
		||||
 "futures-core",
 | 
			
		||||
 "futures-util",
 | 
			
		||||
 "mio",
 | 
			
		||||
 "socket2",
 | 
			
		||||
 "socket2 0.5.9",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "tracing",
 | 
			
		||||
]
 | 
			
		||||
@@ -223,7 +223,7 @@ dependencies = [
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "serde_urlencoded",
 | 
			
		||||
 "smallvec",
 | 
			
		||||
 "socket2",
 | 
			
		||||
 "socket2 0.5.9",
 | 
			
		||||
 "time",
 | 
			
		||||
 "tracing",
 | 
			
		||||
 "url",
 | 
			
		||||
@@ -258,6 +258,7 @@ dependencies = [
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "log",
 | 
			
		||||
 "num_cpus",
 | 
			
		||||
 "redis",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "tera",
 | 
			
		||||
@@ -652,6 +653,20 @@ version = "1.0.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "combine"
 | 
			
		||||
version = "4.6.7"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "futures-core",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "tokio-util",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "config"
 | 
			
		||||
version = "0.14.1"
 | 
			
		||||
@@ -1941,6 +1956,27 @@ dependencies = [
 | 
			
		||||
 "getrandom 0.3.2",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "redis"
 | 
			
		||||
version = "0.23.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "async-trait",
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "combine",
 | 
			
		||||
 "futures-util",
 | 
			
		||||
 "itoa",
 | 
			
		||||
 "percent-encoding",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "ryu",
 | 
			
		||||
 "sha1_smol",
 | 
			
		||||
 "socket2 0.4.10",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "tokio-util",
 | 
			
		||||
 "url",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "redox_syscall"
 | 
			
		||||
version = "0.5.11"
 | 
			
		||||
@@ -2119,6 +2155,12 @@ dependencies = [
 | 
			
		||||
 "digest",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sha1_smol"
 | 
			
		||||
version = "1.0.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sha2"
 | 
			
		||||
version = "0.10.8"
 | 
			
		||||
@@ -2176,6 +2218,16 @@ version = "1.15.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "socket2"
 | 
			
		||||
version = "0.4.10"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "socket2"
 | 
			
		||||
version = "0.5.9"
 | 
			
		||||
@@ -2325,7 +2377,7 @@ dependencies = [
 | 
			
		||||
 "parking_lot",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "signal-hook-registry",
 | 
			
		||||
 "socket2",
 | 
			
		||||
 "socket2 0.5.9",
 | 
			
		||||
 "windows-sys 0.52.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@@ -2638,6 +2690,22 @@ dependencies = [
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi"
 | 
			
		||||
version = "0.3.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "winapi-i686-pc-windows-gnu",
 | 
			
		||||
 "winapi-x86_64-pc-windows-gnu",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi-i686-pc-windows-gnu"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi-util"
 | 
			
		||||
version = "0.1.9"
 | 
			
		||||
@@ -2647,6 +2715,12 @@ dependencies = [
 | 
			
		||||
 "windows-sys 0.59.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi-x86_64-pc-windows-gnu"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows-core"
 | 
			
		||||
version = "0.61.0"
 | 
			
		||||
 
 | 
			
		||||
@@ -21,3 +21,4 @@ actix-identity = "0.6.0"
 | 
			
		||||
bcrypt = "0.15.0"
 | 
			
		||||
uuid = { version = "1.6.1", features = ["v4", "serde"] }
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
redis = { version = "0.23.0", features = ["tokio-comp"] }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										56
									
								
								actix_mvc_app/specs.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								actix_mvc_app/specs.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
- login
 | 
			
		||||
- change profile (email(s), linkedin, websites, telnr's, ...)
 | 
			
		||||
- KYC
 | 
			
		||||
- wallet 
 | 
			
		||||
  - with EUR/CHF/USD, ability to topup wallet... (stripe, make sure to describe well)
 | 
			
		||||
  - with real world digital assets (RWDA)
 | 
			
		||||
  - the RWDA can be transfered to someone else if allowed
 | 
			
		||||
- tickets (see own tickets)
 | 
			
		||||
  - a ticket can have workflow attached to it (as set by rhai script)
 | 
			
		||||
  - on a workflow step needs to be clear for user if they need to do something
 | 
			
		||||
  - has type, subject, priority, level, comments (user can add comment)
 | 
			
		||||
  - if new comment message is sent to inbox as well, if new action needed as well
 | 
			
		||||
- inbox
 | 
			
		||||
  - info we can send to the user, user can reply
 | 
			
		||||
  - has labels
 | 
			
		||||
- actions
 | 
			
		||||
  - if something to do for user, is part of the workflow
 | 
			
		||||
- RWDA'S
 | 
			
		||||
  - overview (in tiles like blogs) of the RWDA's, has tags user can filter
 | 
			
		||||
  - if user clicks on one then goes to mini site (like ebook), is shown in app
 | 
			
		||||
  - on RWDA we see nr of RWDA (marketcap, ... and other core financials, ...)
 | 
			
		||||
  - use can select RWDA, and buy into it, if not enough cash will be asked to put more cash in
 | 
			
		||||
- contracts
 | 
			
		||||
  - as markdown
 | 
			
		||||
  - user can sign
 | 
			
		||||
  - see who signed
 | 
			
		||||
 | 
			
		||||
# user flows
 | 
			
		||||
 | 
			
		||||
## registration
 | 
			
		||||
 | 
			
		||||
- login, user choses secret (done by means of the webassembly component)
 | 
			
		||||
- verification level, user can do KYC
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# rwda
 | 
			
		||||
 | 
			
		||||
- name
 | 
			
		||||
- description
 | 
			
		||||
- link to website
 | 
			
		||||
- nr of shares
 | 
			
		||||
- share value
 | 
			
		||||
- vesting period, lockin period
 | 
			
		||||
- symbol
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Dynex
 | 
			
		||||
 | 
			
		||||
- meeting with 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										389
									
								
								actix_mvc_app/src/controllers/calendar.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										389
									
								
								actix_mvc_app/src/controllers/calendar.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,389 @@
 | 
			
		||||
use actix_web::{web, HttpResponse, Responder, Result};
 | 
			
		||||
use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use tera::Tera;
 | 
			
		||||
 | 
			
		||||
use crate::models::{CalendarEvent, CalendarViewMode};
 | 
			
		||||
use crate::utils::RedisCalendarService;
 | 
			
		||||
 | 
			
		||||
/// Controller for handling calendar-related routes
 | 
			
		||||
pub struct CalendarController;
 | 
			
		||||
 | 
			
		||||
impl CalendarController {
 | 
			
		||||
    /// Handles the calendar page route
 | 
			
		||||
    pub async fn calendar(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        query: web::Query<CalendarQuery>,
 | 
			
		||||
    ) -> Result<impl Responder> {
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "calendar");
 | 
			
		||||
        
 | 
			
		||||
        // Parse the view mode from the query parameters
 | 
			
		||||
        let view_mode = CalendarViewMode::from_str(&query.view.clone().unwrap_or_else(|| "month".to_string()));
 | 
			
		||||
        ctx.insert("view_mode", &view_mode.to_str());
 | 
			
		||||
        
 | 
			
		||||
        // Parse the date from the query parameters or use the current date
 | 
			
		||||
        let date = if let Some(date_str) = &query.date {
 | 
			
		||||
            match NaiveDate::parse_from_str(date_str, "%Y-%m-%d") {
 | 
			
		||||
                Ok(naive_date) => Utc.from_utc_date(&naive_date).and_hms_opt(0, 0, 0).unwrap(),
 | 
			
		||||
                Err(_) => Utc::now(),
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            Utc::now()
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        ctx.insert("current_date", &date.format("%Y-%m-%d").to_string());
 | 
			
		||||
        ctx.insert("current_year", &date.year());
 | 
			
		||||
        ctx.insert("current_month", &date.month());
 | 
			
		||||
        ctx.insert("current_day", &date.day());
 | 
			
		||||
        
 | 
			
		||||
        // Get events for the current view
 | 
			
		||||
        let (start_date, end_date) = match view_mode {
 | 
			
		||||
            CalendarViewMode::Year => {
 | 
			
		||||
                let start = Utc.with_ymd_and_hms(date.year(), 1, 1, 0, 0, 0).unwrap();
 | 
			
		||||
                let end = Utc.with_ymd_and_hms(date.year(), 12, 31, 23, 59, 59).unwrap();
 | 
			
		||||
                (start, end)
 | 
			
		||||
            },
 | 
			
		||||
            CalendarViewMode::Month => {
 | 
			
		||||
                let start = Utc.with_ymd_and_hms(date.year(), date.month(), 1, 0, 0, 0).unwrap();
 | 
			
		||||
                let last_day = Self::last_day_of_month(date.year(), date.month());
 | 
			
		||||
                let end = Utc.with_ymd_and_hms(date.year(), date.month(), last_day, 23, 59, 59).unwrap();
 | 
			
		||||
                (start, end)
 | 
			
		||||
            },
 | 
			
		||||
            CalendarViewMode::Week => {
 | 
			
		||||
                // Calculate the start of the week (Sunday)
 | 
			
		||||
                let weekday = date.weekday().num_days_from_sunday();
 | 
			
		||||
                let start_date = date.date_naive().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap();
 | 
			
		||||
                let start = Utc.from_utc_date(&start_date).and_hms_opt(0, 0, 0).unwrap();
 | 
			
		||||
                let end = start + chrono::Duration::days(7);
 | 
			
		||||
                (start, end)
 | 
			
		||||
            },
 | 
			
		||||
            CalendarViewMode::Day => {
 | 
			
		||||
                let start = Utc.with_ymd_and_hms(date.year(), date.month(), date.day(), 0, 0, 0).unwrap();
 | 
			
		||||
                let end = Utc.with_ymd_and_hms(date.year(), date.month(), date.day(), 23, 59, 59).unwrap();
 | 
			
		||||
                (start, end)
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Get events from Redis
 | 
			
		||||
        let events = match RedisCalendarService::get_events_in_range(start_date, end_date) {
 | 
			
		||||
            Ok(events) => events,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Failed to get events from Redis: {}", e);
 | 
			
		||||
                vec![]
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        ctx.insert("events", &events);
 | 
			
		||||
        
 | 
			
		||||
        // Generate calendar data based on the view mode
 | 
			
		||||
        match view_mode {
 | 
			
		||||
            CalendarViewMode::Year => {
 | 
			
		||||
                let months = (1..=12).map(|month| {
 | 
			
		||||
                    let month_name = match month {
 | 
			
		||||
                        1 => "January",
 | 
			
		||||
                        2 => "February",
 | 
			
		||||
                        3 => "March",
 | 
			
		||||
                        4 => "April",
 | 
			
		||||
                        5 => "May",
 | 
			
		||||
                        6 => "June",
 | 
			
		||||
                        7 => "July",
 | 
			
		||||
                        8 => "August",
 | 
			
		||||
                        9 => "September",
 | 
			
		||||
                        10 => "October",
 | 
			
		||||
                        11 => "November",
 | 
			
		||||
                        12 => "December",
 | 
			
		||||
                        _ => "",
 | 
			
		||||
                    };
 | 
			
		||||
                    
 | 
			
		||||
                    let month_events = events.iter()
 | 
			
		||||
                        .filter(|event| {
 | 
			
		||||
                            event.start_time.month() == month || event.end_time.month() == month
 | 
			
		||||
                        })
 | 
			
		||||
                        .cloned()
 | 
			
		||||
                        .collect::<Vec<_>>();
 | 
			
		||||
                    
 | 
			
		||||
                    CalendarMonth {
 | 
			
		||||
                        month,
 | 
			
		||||
                        name: month_name.to_string(),
 | 
			
		||||
                        events: month_events,
 | 
			
		||||
                    }
 | 
			
		||||
                }).collect::<Vec<_>>();
 | 
			
		||||
                
 | 
			
		||||
                ctx.insert("months", &months);
 | 
			
		||||
            },
 | 
			
		||||
            CalendarViewMode::Month => {
 | 
			
		||||
                let days_in_month = Self::last_day_of_month(date.year(), date.month());
 | 
			
		||||
                let first_day = Utc.with_ymd_and_hms(date.year(), date.month(), 1, 0, 0, 0).unwrap();
 | 
			
		||||
                let first_weekday = first_day.weekday().num_days_from_sunday();
 | 
			
		||||
                
 | 
			
		||||
                let mut calendar_days = Vec::new();
 | 
			
		||||
                
 | 
			
		||||
                // Add empty days for the start of the month
 | 
			
		||||
                for _ in 0..first_weekday {
 | 
			
		||||
                    calendar_days.push(CalendarDay {
 | 
			
		||||
                        day: 0,
 | 
			
		||||
                        events: vec![],
 | 
			
		||||
                        is_current_month: false,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // Add days for the current month
 | 
			
		||||
                for day in 1..=days_in_month {
 | 
			
		||||
                    let day_events = events.iter()
 | 
			
		||||
                        .filter(|event| {
 | 
			
		||||
                            let day_start = Utc.with_ymd_and_hms(date.year(), date.month(), day, 0, 0, 0).unwrap();
 | 
			
		||||
                            let day_end = Utc.with_ymd_and_hms(date.year(), date.month(), day, 23, 59, 59).unwrap();
 | 
			
		||||
                            
 | 
			
		||||
                            (event.start_time <= day_end && event.end_time >= day_start) ||
 | 
			
		||||
                            (event.all_day && event.start_time.day() <= day && event.end_time.day() >= day)
 | 
			
		||||
                        })
 | 
			
		||||
                        .cloned()
 | 
			
		||||
                        .collect::<Vec<_>>();
 | 
			
		||||
                    
 | 
			
		||||
                    calendar_days.push(CalendarDay {
 | 
			
		||||
                        day,
 | 
			
		||||
                        events: day_events,
 | 
			
		||||
                        is_current_month: true,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // Fill out the rest of the calendar grid (6 rows of 7 days)
 | 
			
		||||
                let remaining_days = 42 - calendar_days.len();
 | 
			
		||||
                for day in 1..=remaining_days {
 | 
			
		||||
                    calendar_days.push(CalendarDay {
 | 
			
		||||
                        day: day as u32,
 | 
			
		||||
                        events: vec![],
 | 
			
		||||
                        is_current_month: false,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                ctx.insert("calendar_days", &calendar_days);
 | 
			
		||||
                ctx.insert("month_name", &Self::month_name(date.month()));
 | 
			
		||||
            },
 | 
			
		||||
            CalendarViewMode::Week => {
 | 
			
		||||
                // Calculate the start of the week (Sunday)
 | 
			
		||||
                let weekday = date.weekday().num_days_from_sunday();
 | 
			
		||||
                let week_start = date - chrono::Duration::days(weekday as i64);
 | 
			
		||||
                
 | 
			
		||||
                let mut week_days = Vec::new();
 | 
			
		||||
                for i in 0..7 {
 | 
			
		||||
                    let day_date = week_start + chrono::Duration::days(i);
 | 
			
		||||
                    let day_events = events.iter()
 | 
			
		||||
                        .filter(|event| {
 | 
			
		||||
                            let day_start = Utc.with_ymd_and_hms(day_date.year(), day_date.month(), day_date.day(), 0, 0, 0).unwrap();
 | 
			
		||||
                            let day_end = Utc.with_ymd_and_hms(day_date.year(), day_date.month(), day_date.day(), 23, 59, 59).unwrap();
 | 
			
		||||
                            
 | 
			
		||||
                            (event.start_time <= day_end && event.end_time >= day_start) ||
 | 
			
		||||
                            (event.all_day && event.start_time.day() <= day_date.day() && event.end_time.day() >= day_date.day())
 | 
			
		||||
                        })
 | 
			
		||||
                        .cloned()
 | 
			
		||||
                        .collect::<Vec<_>>();
 | 
			
		||||
                    
 | 
			
		||||
                    week_days.push(CalendarDay {
 | 
			
		||||
                        day: day_date.day(),
 | 
			
		||||
                        events: day_events,
 | 
			
		||||
                        is_current_month: day_date.month() == date.month(),
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                ctx.insert("week_days", &week_days);
 | 
			
		||||
            },
 | 
			
		||||
            CalendarViewMode::Day => {
 | 
			
		||||
                log::info!("Day view selected");
 | 
			
		||||
                ctx.insert("day_name", &Self::day_name(date.weekday().num_days_from_sunday()));
 | 
			
		||||
                
 | 
			
		||||
                // Add debug info
 | 
			
		||||
                log::info!("Events count: {}", events.len());
 | 
			
		||||
                log::info!("Current date: {}", date.format("%Y-%m-%d"));
 | 
			
		||||
                log::info!("Day name: {}", Self::day_name(date.weekday().num_days_from_sunday()));
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("calendar/index.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Handles the new event page route
 | 
			
		||||
    pub async fn new_event(tmpl: web::Data<Tera>) -> Result<impl Responder> {
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "calendar");
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("calendar/new_event.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Handles the create event route
 | 
			
		||||
    pub async fn create_event(
 | 
			
		||||
        form: web::Form<EventForm>,
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
    ) -> Result<impl Responder> {
 | 
			
		||||
        // Parse the start and end times
 | 
			
		||||
        let start_time = match DateTime::parse_from_rfc3339(&form.start_time) {
 | 
			
		||||
            Ok(dt) => dt.with_timezone(&Utc),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Failed to parse start time: {}", e);
 | 
			
		||||
                return Ok(HttpResponse::BadRequest().body("Invalid start time"));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        let end_time = match DateTime::parse_from_rfc3339(&form.end_time) {
 | 
			
		||||
            Ok(dt) => dt.with_timezone(&Utc),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Failed to parse end time: {}", e);
 | 
			
		||||
                return Ok(HttpResponse::BadRequest().body("Invalid end time"));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Create the event
 | 
			
		||||
        let event = CalendarEvent::new(
 | 
			
		||||
            form.title.clone(),
 | 
			
		||||
            form.description.clone(),
 | 
			
		||||
            start_time,
 | 
			
		||||
            end_time,
 | 
			
		||||
            Some(form.color.clone()),
 | 
			
		||||
            form.all_day,
 | 
			
		||||
            None, // User ID would come from session in a real app
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Save the event to Redis
 | 
			
		||||
        match RedisCalendarService::save_event(&event) {
 | 
			
		||||
            Ok(_) => {
 | 
			
		||||
                // Redirect to the calendar page
 | 
			
		||||
                Ok(HttpResponse::SeeOther()
 | 
			
		||||
                    .append_header(("Location", "/calendar"))
 | 
			
		||||
                    .finish())
 | 
			
		||||
            },
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Failed to save event to Redis: {}", e);
 | 
			
		||||
                
 | 
			
		||||
                // Show an error message
 | 
			
		||||
                let mut ctx = tera::Context::new();
 | 
			
		||||
                ctx.insert("active_page", "calendar");
 | 
			
		||||
                ctx.insert("error", "Failed to save event");
 | 
			
		||||
                
 | 
			
		||||
                let rendered = tmpl.render("calendar/new_event.html", &ctx)
 | 
			
		||||
                    .map_err(|e| {
 | 
			
		||||
                        eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                        actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
                    })?;
 | 
			
		||||
                
 | 
			
		||||
                Ok(HttpResponse::InternalServerError().content_type("text/html").body(rendered))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Handles the delete event route
 | 
			
		||||
    pub async fn delete_event(
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
    ) -> Result<impl Responder> {
 | 
			
		||||
        let id = path.into_inner();
 | 
			
		||||
        
 | 
			
		||||
        // Delete the event from Redis
 | 
			
		||||
        match RedisCalendarService::delete_event(&id) {
 | 
			
		||||
            Ok(_) => {
 | 
			
		||||
                // Redirect to the calendar page
 | 
			
		||||
                Ok(HttpResponse::SeeOther()
 | 
			
		||||
                    .append_header(("Location", "/calendar"))
 | 
			
		||||
                    .finish())
 | 
			
		||||
            },
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Failed to delete event from Redis: {}", e);
 | 
			
		||||
                Ok(HttpResponse::InternalServerError().body("Failed to delete event"))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Returns the last day of the month
 | 
			
		||||
    fn last_day_of_month(year: i32, month: u32) -> u32 {
 | 
			
		||||
        match month {
 | 
			
		||||
            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
 | 
			
		||||
            4 | 6 | 9 | 11 => 30,
 | 
			
		||||
            2 => {
 | 
			
		||||
                if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
 | 
			
		||||
                    29
 | 
			
		||||
                } else {
 | 
			
		||||
                    28
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            _ => 30, // Default to 30 days
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Returns the name of the month
 | 
			
		||||
    fn month_name(month: u32) -> &'static str {
 | 
			
		||||
        match month {
 | 
			
		||||
            1 => "January",
 | 
			
		||||
            2 => "February",
 | 
			
		||||
            3 => "March",
 | 
			
		||||
            4 => "April",
 | 
			
		||||
            5 => "May",
 | 
			
		||||
            6 => "June",
 | 
			
		||||
            7 => "July",
 | 
			
		||||
            8 => "August",
 | 
			
		||||
            9 => "September",
 | 
			
		||||
            10 => "October",
 | 
			
		||||
            11 => "November",
 | 
			
		||||
            12 => "December",
 | 
			
		||||
            _ => "",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Returns the name of the day
 | 
			
		||||
    fn day_name(day: u32) -> &'static str {
 | 
			
		||||
        match day {
 | 
			
		||||
            0 => "Sunday",
 | 
			
		||||
            1 => "Monday",
 | 
			
		||||
            2 => "Tuesday",
 | 
			
		||||
            3 => "Wednesday",
 | 
			
		||||
            4 => "Thursday",
 | 
			
		||||
            5 => "Friday",
 | 
			
		||||
            6 => "Saturday",
 | 
			
		||||
            _ => "",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Query parameters for the calendar page
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct CalendarQuery {
 | 
			
		||||
    pub view: Option<String>,
 | 
			
		||||
    pub date: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Form data for creating an event
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct EventForm {
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    pub start_time: String,
 | 
			
		||||
    pub end_time: String,
 | 
			
		||||
    pub color: String,
 | 
			
		||||
    pub all_day: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a day in the calendar
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
struct CalendarDay {
 | 
			
		||||
    day: u32,
 | 
			
		||||
    events: Vec<CalendarEvent>,
 | 
			
		||||
    is_current_month: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a month in the calendar
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
struct CalendarMonth {
 | 
			
		||||
    month: u32,
 | 
			
		||||
    name: String,
 | 
			
		||||
    events: Vec<CalendarEvent>,
 | 
			
		||||
}
 | 
			
		||||
@@ -2,8 +2,10 @@
 | 
			
		||||
pub mod home;
 | 
			
		||||
pub mod auth;
 | 
			
		||||
pub mod ticket;
 | 
			
		||||
pub mod calendar;
 | 
			
		||||
 | 
			
		||||
// Re-export controllers for easier imports
 | 
			
		||||
pub use home::HomeController;
 | 
			
		||||
pub use auth::AuthController;
 | 
			
		||||
pub use ticket::TicketController;
 | 
			
		||||
pub use ticket::TicketController;
 | 
			
		||||
pub use calendar::CalendarController;
 | 
			
		||||
@@ -13,6 +13,7 @@ mod utils;
 | 
			
		||||
 | 
			
		||||
// Import middleware components
 | 
			
		||||
use app_middleware::{RequestTimer, SecurityHeaders};
 | 
			
		||||
use utils::redis_service;
 | 
			
		||||
 | 
			
		||||
// Initialize lazy_static for in-memory storage
 | 
			
		||||
#[macro_use]
 | 
			
		||||
@@ -28,6 +29,15 @@ async fn main() -> io::Result<()> {
 | 
			
		||||
    let config = config::get_config();
 | 
			
		||||
    let bind_address = format!("{}:{}", config.server.host, config.server.port);
 | 
			
		||||
    
 | 
			
		||||
    // Initialize Redis client
 | 
			
		||||
    let redis_url = std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
 | 
			
		||||
    if let Err(e) = redis_service::init_redis_client(&redis_url) {
 | 
			
		||||
        log::error!("Failed to initialize Redis client: {}", e);
 | 
			
		||||
        log::warn!("Calendar functionality will not work properly without Redis");
 | 
			
		||||
    } else {
 | 
			
		||||
        log::info!("Redis client initialized successfully");
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    log::info!("Starting server at http://{}", bind_address);
 | 
			
		||||
    
 | 
			
		||||
    // Create and configure the HTTP server
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								actix_mvc_app/src/models/calendar.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								actix_mvc_app/src/models/calendar.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
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
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
			
		||||
pub enum CalendarViewMode {
 | 
			
		||||
    /// Year view
 | 
			
		||||
    Year,
 | 
			
		||||
    /// Month view
 | 
			
		||||
    Month,
 | 
			
		||||
    /// Week view
 | 
			
		||||
    Week,
 | 
			
		||||
    /// Day view
 | 
			
		||||
    Day,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl CalendarViewMode {
 | 
			
		||||
    /// Converts a string to a view mode
 | 
			
		||||
    pub fn from_str(s: &str) -> Self {
 | 
			
		||||
        match s.to_lowercase().as_str() {
 | 
			
		||||
            "year" => Self::Year,
 | 
			
		||||
            "month" => Self::Month,
 | 
			
		||||
            "week" => Self::Week,
 | 
			
		||||
            "day" => Self::Day,
 | 
			
		||||
            _ => Self::Month, // Default to month view
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Converts the view mode to a string
 | 
			
		||||
    pub fn to_str(&self) -> &'static str {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Year => "year",
 | 
			
		||||
            Self::Month => "month",
 | 
			
		||||
            Self::Week => "week",
 | 
			
		||||
            Self::Day => "day",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
// Export models
 | 
			
		||||
pub mod user;
 | 
			
		||||
pub mod ticket;
 | 
			
		||||
pub mod calendar;
 | 
			
		||||
 | 
			
		||||
// Re-export models for easier imports
 | 
			
		||||
pub use user::User;
 | 
			
		||||
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority, TicketFilter};
 | 
			
		||||
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority, TicketFilter};
 | 
			
		||||
pub use calendar::{CalendarEvent, CalendarViewMode};
 | 
			
		||||
@@ -4,6 +4,7 @@ use actix_web::cookie::Key;
 | 
			
		||||
use crate::controllers::home::HomeController;
 | 
			
		||||
use crate::controllers::auth::AuthController;
 | 
			
		||||
use crate::controllers::ticket::TicketController;
 | 
			
		||||
use crate::controllers::calendar::CalendarController;
 | 
			
		||||
 | 
			
		||||
/// Configures all application routes
 | 
			
		||||
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
 | 
			
		||||
@@ -42,5 +43,11 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
 | 
			
		||||
            .route("/tickets/{id}", web::get().to(TicketController::show_ticket))
 | 
			
		||||
            .route("/tickets/{id}/comment", web::post().to(TicketController::add_comment))
 | 
			
		||||
            .route("/tickets/{id}/status/{status}", web::get().to(TicketController::update_status))
 | 
			
		||||
            
 | 
			
		||||
            // Calendar routes
 | 
			
		||||
            .route("/calendar", web::get().to(CalendarController::calendar))
 | 
			
		||||
            .route("/calendar/new", web::get().to(CalendarController::new_event))
 | 
			
		||||
            .route("/calendar/new", web::post().to(CalendarController::create_event))
 | 
			
		||||
            .route("/calendar/{id}/delete", web::get().to(CalendarController::delete_event))
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,12 @@
 | 
			
		||||
use chrono::{DateTime, TimeZone, Utc};
 | 
			
		||||
use tera::{self, Function, Result, Value};
 | 
			
		||||
 | 
			
		||||
// Export modules
 | 
			
		||||
pub mod redis_service;
 | 
			
		||||
 | 
			
		||||
// Re-export for easier imports
 | 
			
		||||
pub use redis_service::RedisCalendarService;
 | 
			
		||||
 | 
			
		||||
/// Registers custom Tera functions
 | 
			
		||||
pub fn register_tera_functions(tera: &mut tera::Tera) {
 | 
			
		||||
    tera.register_function("now", NowFunction);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										144
									
								
								actix_mvc_app/src/utils/redis_service.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								actix_mvc_app/src/utils/redis_service.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
use redis::{Client, Commands, Connection, RedisError};
 | 
			
		||||
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
 | 
			
		||||
lazy_static! {
 | 
			
		||||
    static ref REDIS_CLIENT: Arc<Mutex<Option<Client>>> = Arc::new(Mutex::new(None));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Initialize the Redis client
 | 
			
		||||
pub fn init_redis_client(redis_url: &str) -> Result<(), RedisError> {
 | 
			
		||||
    let client = redis::Client::open(redis_url)?;
 | 
			
		||||
    
 | 
			
		||||
    // Test the connection
 | 
			
		||||
    let _: Connection = client.get_connection()?;
 | 
			
		||||
    
 | 
			
		||||
    // Store the client in the lazy static
 | 
			
		||||
    let mut client_guard = REDIS_CLIENT.lock().unwrap();
 | 
			
		||||
    *client_guard = Some(client);
 | 
			
		||||
    
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get a Redis connection
 | 
			
		||||
pub fn get_connection() -> Result<Connection, RedisError> {
 | 
			
		||||
    let client_guard = REDIS_CLIENT.lock().unwrap();
 | 
			
		||||
    
 | 
			
		||||
    if let Some(client) = &*client_guard {
 | 
			
		||||
        client.get_connection()
 | 
			
		||||
    } else {
 | 
			
		||||
        Err(RedisError::from(std::io::Error::new(
 | 
			
		||||
            std::io::ErrorKind::NotConnected,
 | 
			
		||||
            "Redis client not initialized",
 | 
			
		||||
        )))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Redis service for calendar events
 | 
			
		||||
pub struct RedisCalendarService;
 | 
			
		||||
 | 
			
		||||
impl RedisCalendarService {
 | 
			
		||||
    /// Key prefix for calendar events
 | 
			
		||||
    const EVENT_KEY_PREFIX: &'static str = "calendar:event:";
 | 
			
		||||
    
 | 
			
		||||
    /// Key for the set of all event IDs
 | 
			
		||||
    const ALL_EVENTS_KEY: &'static str = "calendar:all_events";
 | 
			
		||||
    
 | 
			
		||||
    /// Save a calendar event to Redis
 | 
			
		||||
    pub fn save_event(event: &CalendarEvent) -> Result<(), RedisError> {
 | 
			
		||||
        let mut conn = get_connection()?;
 | 
			
		||||
        
 | 
			
		||||
        // Convert the event to JSON
 | 
			
		||||
        let json = event.to_json().map_err(|e| {
 | 
			
		||||
            RedisError::from(std::io::Error::new(
 | 
			
		||||
                std::io::ErrorKind::InvalidData,
 | 
			
		||||
                format!("Failed to serialize event: {}", e),
 | 
			
		||||
            ))
 | 
			
		||||
        })?;
 | 
			
		||||
        
 | 
			
		||||
        // Save the event
 | 
			
		||||
        let event_key = format!("{}{}", Self::EVENT_KEY_PREFIX, event.id);
 | 
			
		||||
        let _: () = conn.set(event_key, json)?;
 | 
			
		||||
        
 | 
			
		||||
        // Add the event ID to the set of all events
 | 
			
		||||
        let _: () = conn.sadd(Self::ALL_EVENTS_KEY, &event.id)?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Get a calendar event from Redis by ID
 | 
			
		||||
    pub fn get_event(id: &str) -> Result<Option<CalendarEvent>, RedisError> {
 | 
			
		||||
        let mut conn = get_connection()?;
 | 
			
		||||
        
 | 
			
		||||
        // Get the event JSON
 | 
			
		||||
        let event_key = format!("{}{}", Self::EVENT_KEY_PREFIX, id);
 | 
			
		||||
        let json: Option<String> = conn.get(event_key)?;
 | 
			
		||||
        
 | 
			
		||||
        // Parse the JSON
 | 
			
		||||
        if let Some(json) = json {
 | 
			
		||||
            let event = CalendarEvent::from_json(&json).map_err(|e| {
 | 
			
		||||
                RedisError::from(std::io::Error::new(
 | 
			
		||||
                    std::io::ErrorKind::InvalidData,
 | 
			
		||||
                    format!("Failed to deserialize event: {}", e),
 | 
			
		||||
                ))
 | 
			
		||||
            })?;
 | 
			
		||||
            
 | 
			
		||||
            Ok(Some(event))
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(None)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Delete a calendar event from Redis
 | 
			
		||||
    pub fn delete_event(id: &str) -> Result<bool, RedisError> {
 | 
			
		||||
        let mut conn = get_connection()?;
 | 
			
		||||
        
 | 
			
		||||
        // Delete the event
 | 
			
		||||
        let event_key = format!("{}{}", Self::EVENT_KEY_PREFIX, id);
 | 
			
		||||
        let deleted: i32 = conn.del(event_key)?;
 | 
			
		||||
        
 | 
			
		||||
        // Remove the event ID from the set of all events
 | 
			
		||||
        let _: () = conn.srem(Self::ALL_EVENTS_KEY, id)?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(deleted > 0)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Get all calendar events from Redis
 | 
			
		||||
    pub fn get_all_events() -> Result<Vec<CalendarEvent>, RedisError> {
 | 
			
		||||
        let mut conn = get_connection()?;
 | 
			
		||||
        
 | 
			
		||||
        // Get all event IDs
 | 
			
		||||
        let event_ids: Vec<String> = conn.smembers(Self::ALL_EVENTS_KEY)?;
 | 
			
		||||
        
 | 
			
		||||
        // Get all events
 | 
			
		||||
        let mut events = Vec::new();
 | 
			
		||||
        for id in event_ids {
 | 
			
		||||
            if let Some(event) = Self::get_event(&id)? {
 | 
			
		||||
                events.push(event);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Ok(events)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Get events for a specific date range
 | 
			
		||||
    pub fn get_events_in_range(
 | 
			
		||||
        start: chrono::DateTime<chrono::Utc>,
 | 
			
		||||
        end: chrono::DateTime<chrono::Utc>,
 | 
			
		||||
    ) -> Result<Vec<CalendarEvent>, RedisError> {
 | 
			
		||||
        let all_events = Self::get_all_events()?;
 | 
			
		||||
        
 | 
			
		||||
        // Filter events that fall within the date range
 | 
			
		||||
        let filtered_events = all_events
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .filter(|event| {
 | 
			
		||||
                // Check if the event overlaps with the date range
 | 
			
		||||
                (event.start_time <= end && event.end_time >= start)
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
        Ok(filtered_events)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -32,6 +32,9 @@
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'editor' %}active{% endif %}" href="/editor">Markdown Editor</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'calendar' %}active{% endif %}" href="/calendar">Calendar</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                </ul>
 | 
			
		||||
                <ul class="navbar-nav ms-auto">
 | 
			
		||||
                    {% if user and user.id %}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										132
									
								
								actix_mvc_app/src/views/calendar/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								actix_mvc_app/src/views/calendar/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Calendar{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <h1>Calendar</h1>
 | 
			
		||||
    
 | 
			
		||||
    <p>View Mode: {{ view_mode }}</p>
 | 
			
		||||
    <p>Current Date: {{ current_date }}</p>
 | 
			
		||||
    
 | 
			
		||||
    <div class="d-flex justify-content-between align-items-center mb-3">
 | 
			
		||||
        <div class="btn-group">
 | 
			
		||||
            <a href="/calendar?view=day" class="btn btn-outline-primary">Day</a>
 | 
			
		||||
            <a href="/calendar?view=month" class="btn btn-outline-primary">Month</a>
 | 
			
		||||
            <a href="/calendar?view=year" class="btn btn-outline-primary">Year</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <a href="/calendar/new" class="btn btn-success">
 | 
			
		||||
            <i class="bi bi-plus-circle"></i> Create New Event
 | 
			
		||||
        </a>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    {% if view_mode == "month" %}
 | 
			
		||||
        <h2>Month View: {{ month_name }} {{ current_year }}</h2>
 | 
			
		||||
        
 | 
			
		||||
        <table class="table table-bordered">
 | 
			
		||||
            <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th>Sun</th>
 | 
			
		||||
                    <th>Mon</th>
 | 
			
		||||
                    <th>Tue</th>
 | 
			
		||||
                    <th>Wed</th>
 | 
			
		||||
                    <th>Thu</th>
 | 
			
		||||
                    <th>Fri</th>
 | 
			
		||||
                    <th>Sat</th>
 | 
			
		||||
                </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
                {% for week in range(start=0, end=6) %}
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        {% for day_idx in range(start=0, end=7) %}
 | 
			
		||||
                            <td>
 | 
			
		||||
                                {% set idx = week * 7 + day_idx %}
 | 
			
		||||
                                {% if idx < calendar_days|length %}
 | 
			
		||||
                                    {% set day = calendar_days[idx] %}
 | 
			
		||||
                                    {% if day.day > 0 %}
 | 
			
		||||
                                        {{ day.day }}
 | 
			
		||||
                                    {% endif %}
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </td>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </tr>
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
            </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
    {% elif view_mode == "year" %}
 | 
			
		||||
        <h2>Year View: {{ current_year }}</h2>
 | 
			
		||||
        
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            {% for month in months %}
 | 
			
		||||
                <div class="col-md-4 mb-3">
 | 
			
		||||
                    <div class="card">
 | 
			
		||||
                        <div class="card-header">{{ month.name }}</div>
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <p>Events: {{ month.events|length }}</p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </div>
 | 
			
		||||
    {% elif view_mode == "day" %}
 | 
			
		||||
        <h2>Day View: {{ current_date }}</h2>
 | 
			
		||||
        
 | 
			
		||||
        <div class="card mb-4">
 | 
			
		||||
            <div class="card-header bg-primary text-white">
 | 
			
		||||
                All Day Events
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="card-body">
 | 
			
		||||
                {% if events is defined and events|length > 0 %}
 | 
			
		||||
                    {% for event in events %}
 | 
			
		||||
                        {% if event.all_day %}
 | 
			
		||||
                            <div class="alert" style="background-color: {{ event.color }}; color: white;">
 | 
			
		||||
                                <h5>{{ event.title }}</h5>
 | 
			
		||||
                                <p>{{ event.description }}</p>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                {% else %}
 | 
			
		||||
                    <p class="text-muted">No all-day events</p>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="list-group">
 | 
			
		||||
            {% for hour in range(start=0, end=24) %}
 | 
			
		||||
                <div class="list-group-item">
 | 
			
		||||
                    <div class="d-flex">
 | 
			
		||||
                        <div class="pe-3" style="width: 60px; text-align: right;">
 | 
			
		||||
                            <strong>{{ "%02d"|format(value=hour) }}:00</strong>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="flex-grow-1">
 | 
			
		||||
                            {% if events is defined and events|length > 0 %}
 | 
			
		||||
                                {% for event in events %}
 | 
			
		||||
                                    {% if not event.all_day %}
 | 
			
		||||
                                        {% set start_hour = event.start_time|date(format="%H") %}
 | 
			
		||||
                                        {% if start_hour == hour|string %}
 | 
			
		||||
                                            <div class="alert mb-2" style="background-color: {{ event.color }}; color: white;">
 | 
			
		||||
                                                <h5>{{ event.title }}</h5>
 | 
			
		||||
                                                <p>{{ event.start_time|date(format="%H:%M") }} - {{ event.end_time|date(format="%H:%M") }}</p>
 | 
			
		||||
                                                <p>{{ event.description }}</p>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    {% endif %}
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    
 | 
			
		||||
    <!-- Floating Action Button (FAB) for creating new events -->
 | 
			
		||||
    <a href="/calendar/new" class="position-fixed bottom-0 end-0 m-4 btn btn-primary rounded-circle shadow" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; font-size: 24px;">
 | 
			
		||||
        <i class="bi bi-plus"></i>
 | 
			
		||||
    </a>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% block extra_css %}
 | 
			
		||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										98
									
								
								actix_mvc_app/src/views/calendar/new_event.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								actix_mvc_app/src/views/calendar/new_event.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}New Calendar Event{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <h1>Create New Event</h1>
 | 
			
		||||
    
 | 
			
		||||
    {% if error %}
 | 
			
		||||
    <div class="alert alert-danger" role="alert">
 | 
			
		||||
        {{ error }}
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    
 | 
			
		||||
    <form action="/calendar/new" method="post">
 | 
			
		||||
        <div class="mb-3">
 | 
			
		||||
            <label for="title" class="form-label">Event Title</label>
 | 
			
		||||
            <input type="text" class="form-control" id="title" name="title" required>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="mb-3">
 | 
			
		||||
            <label for="description" class="form-label">Description</label>
 | 
			
		||||
            <textarea class="form-control" id="description" name="description" rows="3"></textarea>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="mb-3 form-check">
 | 
			
		||||
            <input type="checkbox" class="form-check-input" id="all_day" name="all_day">
 | 
			
		||||
            <label class="form-check-label" for="all_day">All Day Event</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="row mb-3">
 | 
			
		||||
            <div class="col">
 | 
			
		||||
                <label for="start_time" class="form-label">Start Time</label>
 | 
			
		||||
                <input type="datetime-local" class="form-control" id="start_time" name="start_time" required>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col">
 | 
			
		||||
                <label for="end_time" class="form-label">End Time</label>
 | 
			
		||||
                <input type="datetime-local" class="form-control" id="end_time" name="end_time" required>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="mb-3">
 | 
			
		||||
            <label for="color" class="form-label">Event Color</label>
 | 
			
		||||
            <select class="form-control" id="color" name="color">
 | 
			
		||||
                <option value="#4285F4">Blue</option>
 | 
			
		||||
                <option value="#EA4335">Red</option>
 | 
			
		||||
                <option value="#34A853">Green</option>
 | 
			
		||||
                <option value="#FBBC05">Yellow</option>
 | 
			
		||||
                <option value="#A142F4">Purple</option>
 | 
			
		||||
                <option value="#24C1E0">Cyan</option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="mb-3">
 | 
			
		||||
            <button type="submit" class="btn btn-primary">Create Event</button>
 | 
			
		||||
            <a href="/calendar" class="btn btn-secondary">Cancel</a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
        // Convert datetime-local inputs to RFC3339 format on form submission
 | 
			
		||||
        document.querySelector('form').addEventListener('submit', function(e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            
 | 
			
		||||
            const startTime = document.getElementById('start_time').value;
 | 
			
		||||
            const endTime = document.getElementById('end_time').value;
 | 
			
		||||
            
 | 
			
		||||
            // Convert to RFC3339 format
 | 
			
		||||
            const startRFC = new Date(startTime).toISOString();
 | 
			
		||||
            const endRFC = new Date(endTime).toISOString();
 | 
			
		||||
            
 | 
			
		||||
            // Create hidden inputs for the RFC3339 values
 | 
			
		||||
            const startInput = document.createElement('input');
 | 
			
		||||
            startInput.type = 'hidden';
 | 
			
		||||
            startInput.name = 'start_time';
 | 
			
		||||
            startInput.value = startRFC;
 | 
			
		||||
            
 | 
			
		||||
            const endInput = document.createElement('input');
 | 
			
		||||
            endInput.type = 'hidden';
 | 
			
		||||
            endInput.name = 'end_time';
 | 
			
		||||
            endInput.value = endRFC;
 | 
			
		||||
            
 | 
			
		||||
            // Remove the original inputs
 | 
			
		||||
            document.getElementById('start_time').removeAttribute('name');
 | 
			
		||||
            document.getElementById('end_time').removeAttribute('name');
 | 
			
		||||
            
 | 
			
		||||
            // Add the hidden inputs to the form
 | 
			
		||||
            this.appendChild(startInput);
 | 
			
		||||
            this.appendChild(endInput);
 | 
			
		||||
            
 | 
			
		||||
            // Submit the form
 | 
			
		||||
            this.submit();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user