7645792d5a
The database schema defines owner_id as UUID, but the Calendar entity stored it as String. This caused a type mismatch error when creating calendars via CalDAV clients like DAVx: column "owner_id" is of type uuid but expression is of type text Changes: - Change Calendar.owner_id from String to Uuid - Update new() and with_id() to accept Uuid - Update owner_id() getter to return &Uuid - Update belongs_to() to accept &Uuid - Update calendar_storage_adapter to pass Uuid directly - Update tests to use Uuid::new_v4() Fixes #200
357 lines
9.7 KiB
Rust
Executable File
357 lines
9.7 KiB
Rust
Executable File
use chrono::{DateTime, Utc};
|
|
/**
|
|
* Calendar Entity
|
|
*
|
|
* This module defines the Calendar entity, which represents a calendar in the CalDAV
|
|
* implementation. Calendars contain calendar events and are owned by users.
|
|
*
|
|
* Calendars have properties such as name, color, and description, and they serve as
|
|
* containers for calendar events. Each calendar belongs to a specific user and can
|
|
* have custom properties.
|
|
*/
|
|
use uuid::Uuid;
|
|
|
|
use crate::common::errors::{DomainError, ErrorKind, Result};
|
|
|
|
// Re-export entity errors from the centralized module
|
|
pub use super::entity_errors::CalendarError;
|
|
|
|
/**
|
|
* Calendar entity.
|
|
*
|
|
* Represents a calendar container that can hold multiple calendar events.
|
|
* Each calendar is owned by a user and has properties like name, color, and description.
|
|
*/
|
|
#[derive(Debug, Clone)]
|
|
pub struct Calendar {
|
|
/// Unique identifier for the calendar
|
|
id: Uuid,
|
|
|
|
/// Display name of the calendar
|
|
name: String,
|
|
|
|
/// ID of the user who owns this calendar
|
|
owner_id: Uuid,
|
|
|
|
/// Optional description of the calendar
|
|
description: Option<String>,
|
|
|
|
/// Optional color code for UI display (hex format #RRGGBB)
|
|
color: Option<String>,
|
|
|
|
/// Time when the calendar was created
|
|
created_at: DateTime<Utc>,
|
|
|
|
/// Time when the calendar was last modified
|
|
updated_at: DateTime<Utc>,
|
|
|
|
/// Optional list of custom properties (for extended CalDAV support)
|
|
custom_properties: std::collections::HashMap<String, String>,
|
|
}
|
|
|
|
impl Calendar {
|
|
/**
|
|
* Creates a new calendar with the given properties.
|
|
*
|
|
* @param name Display name of the calendar
|
|
* @param owner_id ID of the user who owns this calendar
|
|
* @param description Optional description of the calendar
|
|
* @param color Optional color code for UI display (#RRGGBB format)
|
|
* @return Result containing the new Calendar or a domain error
|
|
*/
|
|
pub fn new(
|
|
name: String,
|
|
owner_id: Uuid,
|
|
description: Option<String>,
|
|
color: Option<String>,
|
|
) -> Result<Self> {
|
|
// Validate inputs
|
|
if name.is_empty() {
|
|
return Err(DomainError::new(
|
|
ErrorKind::InvalidInput,
|
|
"Calendar",
|
|
"Calendar name cannot be empty",
|
|
));
|
|
}
|
|
|
|
if let Some(color_str) = &color {
|
|
Self::validate_color(color_str)?;
|
|
}
|
|
|
|
let now = Utc::now();
|
|
|
|
Ok(Self {
|
|
id: Uuid::new_v4(),
|
|
name,
|
|
owner_id,
|
|
description,
|
|
color,
|
|
created_at: now,
|
|
updated_at: now,
|
|
custom_properties: std::collections::HashMap::new(),
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Creates a calendar with specific ID and timestamps.
|
|
* Typically used when reconstructing from storage.
|
|
*
|
|
* @param id Unique identifier for the calendar
|
|
* @param name Display name of the calendar
|
|
* @param owner_id ID of the user who owns this calendar
|
|
* @param description Optional description of the calendar
|
|
* @param color Optional color code for UI display
|
|
* @param created_at Time when the calendar was created
|
|
* @param updated_at Time when the calendar was last modified
|
|
* @return Result containing the new Calendar or a domain error
|
|
*/
|
|
pub fn with_id(
|
|
id: Uuid,
|
|
name: String,
|
|
owner_id: Uuid,
|
|
description: Option<String>,
|
|
color: Option<String>,
|
|
created_at: DateTime<Utc>,
|
|
updated_at: DateTime<Utc>,
|
|
) -> Result<Self> {
|
|
// Basic validation
|
|
if name.is_empty() {
|
|
return Err(DomainError::new(
|
|
ErrorKind::InvalidInput,
|
|
"Calendar",
|
|
"Calendar name cannot be empty",
|
|
));
|
|
}
|
|
|
|
if let Some(color_str) = &color {
|
|
Self::validate_color(color_str)?;
|
|
}
|
|
|
|
Ok(Self {
|
|
id,
|
|
name,
|
|
owner_id,
|
|
description,
|
|
color,
|
|
created_at,
|
|
updated_at,
|
|
custom_properties: std::collections::HashMap::new(),
|
|
})
|
|
}
|
|
|
|
// Getters
|
|
|
|
/// Returns the calendar's unique identifier
|
|
pub fn id(&self) -> &Uuid {
|
|
&self.id
|
|
}
|
|
|
|
/// Returns the calendar's display name
|
|
pub fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
|
|
/// Returns the ID of the user who owns this calendar
|
|
pub fn owner_id(&self) -> &Uuid {
|
|
&self.owner_id
|
|
}
|
|
|
|
/// Returns the calendar's description, if any
|
|
pub fn description(&self) -> Option<&str> {
|
|
self.description.as_deref()
|
|
}
|
|
|
|
/// Returns the calendar's color code, if any
|
|
pub fn color(&self) -> Option<&str> {
|
|
self.color.as_deref()
|
|
}
|
|
|
|
/// Returns the time when the calendar was created
|
|
pub fn created_at(&self) -> &DateTime<Utc> {
|
|
&self.created_at
|
|
}
|
|
|
|
/// Returns the time when the calendar was last modified
|
|
pub fn updated_at(&self) -> &DateTime<Utc> {
|
|
&self.updated_at
|
|
}
|
|
|
|
/// Returns a custom property value by name, if it exists
|
|
pub fn custom_property(&self, name: &str) -> Option<&str> {
|
|
self.custom_properties.get(name).map(|s| s.as_str())
|
|
}
|
|
|
|
/// Returns all custom properties
|
|
pub fn custom_properties(&self) -> &std::collections::HashMap<String, String> {
|
|
&self.custom_properties
|
|
}
|
|
|
|
// Setters and Mutators
|
|
|
|
/**
|
|
* Updates the calendar's name.
|
|
*
|
|
* @param name New display name for the calendar
|
|
* @return Result indicating success or containing a domain error
|
|
*/
|
|
pub fn update_name(&mut self, name: String) -> Result<()> {
|
|
if name.is_empty() {
|
|
return Err(DomainError::new(
|
|
ErrorKind::InvalidInput,
|
|
"Calendar",
|
|
"Calendar name cannot be empty",
|
|
));
|
|
}
|
|
|
|
self.name = name;
|
|
self.updated_at = Utc::now();
|
|
Ok(())
|
|
}
|
|
|
|
/**
|
|
* Updates the calendar's description.
|
|
*
|
|
* @param description New description for the calendar
|
|
*/
|
|
pub fn update_description(&mut self, description: Option<String>) {
|
|
self.description = description;
|
|
self.updated_at = Utc::now();
|
|
}
|
|
|
|
/**
|
|
* Updates the calendar's color.
|
|
*
|
|
* @param color New color code for the calendar
|
|
* @return Result indicating success or containing a domain error
|
|
*/
|
|
pub fn update_color(&mut self, color: Option<String>) -> Result<()> {
|
|
// Validate color format if provided
|
|
if let Some(color_str) = &color {
|
|
Self::validate_color(color_str)?;
|
|
}
|
|
|
|
self.color = color;
|
|
self.updated_at = Utc::now();
|
|
Ok(())
|
|
}
|
|
|
|
/// Validate a calendar color
|
|
fn validate_color(color: &str) -> Result<()> {
|
|
if !color.starts_with('#')
|
|
|| !(color.len() == 7 || color.len() == 9)
|
|
|| color[1..].chars().any(|c| !c.is_ascii_hexdigit())
|
|
{
|
|
return Err(DomainError::new(
|
|
ErrorKind::InvalidInput,
|
|
"Calendar",
|
|
"Color must be in #RRGGBB or #RRGGBBAA format",
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/**
|
|
* Sets a custom property for extended CalDAV support.
|
|
*
|
|
* @param name Name of the property
|
|
* @param value Value of the property
|
|
*/
|
|
pub fn set_custom_property(&mut self, name: String, value: String) {
|
|
self.custom_properties.insert(name, value);
|
|
self.updated_at = Utc::now();
|
|
}
|
|
|
|
/**
|
|
* Removes a custom property.
|
|
*
|
|
* @param name Name of the property to remove
|
|
* @return true if the property was removed, false if it didn't exist
|
|
*/
|
|
pub fn remove_custom_property(&mut self, name: &str) -> bool {
|
|
let result = self.custom_properties.remove(name).is_some();
|
|
if result {
|
|
self.updated_at = Utc::now();
|
|
}
|
|
result
|
|
}
|
|
|
|
/**
|
|
* Checks if this calendar belongs to the specified user.
|
|
*
|
|
* @param user_id ID of the user to check ownership against
|
|
* @return true if the calendar belongs to the user, false otherwise
|
|
*/
|
|
pub fn belongs_to(&self, user_id: &Uuid) -> bool {
|
|
self.owner_id == *user_id
|
|
}
|
|
|
|
/**
|
|
* Updates the last modification time of the calendar to now.
|
|
* Called when calendar events are added, modified, or removed.
|
|
*/
|
|
pub fn touch(&mut self) {
|
|
self.updated_at = Utc::now();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_init() {
|
|
let owner_id = Uuid::new_v4();
|
|
let res = Calendar::new("Name".to_string(), owner_id, None, None);
|
|
assert!(res.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_init_color_rgb() {
|
|
let owner_id = Uuid::new_v4();
|
|
let res = Calendar::new(
|
|
"Name".to_string(),
|
|
owner_id,
|
|
None,
|
|
Some("#84FFa9".to_string()),
|
|
);
|
|
assert!(res.is_ok());
|
|
}
|
|
|
|
/// Format as used by the android DAVx app
|
|
#[test]
|
|
fn test_init_color_rgba() {
|
|
let owner_id = Uuid::new_v4();
|
|
let res = Calendar::new(
|
|
"Name".to_string(),
|
|
owner_id,
|
|
None,
|
|
Some("#abcdef51".to_string()),
|
|
);
|
|
assert!(res.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_init_bad_color_1() {
|
|
let owner_id = Uuid::new_v4();
|
|
let res = Calendar::new(
|
|
"Name".to_string(),
|
|
owner_id,
|
|
None,
|
|
Some("foo".to_string()),
|
|
);
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_init_bad_color_2() {
|
|
let owner_id = Uuid::new_v4();
|
|
let res = Calendar::new(
|
|
"Name".to_string(),
|
|
owner_id,
|
|
None,
|
|
Some("#xxjjff".to_string()),
|
|
);
|
|
assert!(res.is_err());
|
|
}
|
|
}
|