import hashlib from dataclasses import dataclass, field, asdict from typing import List, Optional, Dict, Any from datetime import datetime import os import re import logging # Set up logging configuration logging.basicConfig( level=logging.DEBUG, # Set to DEBUG to show all log levels format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(), # Console handler logging.FileHandler('alert_api.log') # File handler ] ) logger = logging.getLogger('AlertApi') @dataclass class RepeatRule: type: str # "daily" or "weekly" or "once" days_of_week: Optional[List[str]] = field(default_factory=list) at: Optional[str] = None def validate(self) -> bool: """Validate repeat rule configuration""" valid_types = {"daily", "weekly", "once"} valid_days = {"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"} if self.type not in valid_types: logger.error(f"Invalid RepeatRule type: '{self.type}'. Must be one of {valid_types}") return False if self.type == "weekly": invalid_days = [day for day in self.days_of_week if day.lower() not in valid_days] if invalid_days: logger.error(f"Invalid RepeatRule weekday names: {invalid_days}. Must be one of {valid_days}") return False if not self.days_of_week: logger.error("Weekly repeat rule must specify at least one day") return False if self.type == "once" and self.days_of_week: if self.days_of_week: logger.error("One-time alert does not support days_of_week") return False if not self.at: logger.error("One-time repeat rule must specify a valid 'at' date") return False try: datetime.strptime(self.at, "%d.%m.%Y") except ValueError: logger.error(f"Invalid 'at' date format: '{self.at}'. Expected format: 'dd.mm.yyyy'") return False logger.debug(f"RepeatRule validation passed: {self.__dict__}") return True @dataclass class Snooze: enabled: bool = True duration: int = 10 # minutes max_count: int = 3 def validate(self) -> bool: """Validate snooze configuration""" if not isinstance(self.enabled, bool): logger.error(f"Snooze enabled must be boolean, got {type(self.enabled)}") return False if not isinstance(self.duration, int): logger.error(f"Snooze duration must be integer, got {type(self.duration)}") return False if self.duration <= 0: logger.error(f"Snooze duration must be positive, got {self.duration}") return False if not isinstance(self.max_count, int): logger.error(f"Snooze max_count must be integer, got {type(self.max_count)}") return False if self.max_count <= 0: logger.error(f"Snooze max_count must be positive, got {self.max_count}") return False logger.debug(f"Snooze validation passed: {self.__dict__}") return True @dataclass class Metadata: volume: int = 100 notes: str = "" md5sum: str = "" def validate(self) -> bool: """Validate metadata configuration""" if not isinstance(self.volume, int): logger.error(f"Volume must be integer, got {type(self.volume)}") return False if not 0 <= self.volume <= 100: logger.error(f"Volume must be between 0 and 100, got {self.volume}") return False logger.debug(f"Metadata validation passed: {self.__dict__}") return True @dataclass class Alarm: name: str time: str # Format: "HH:MM:SS" repeat_rule: RepeatRule file_to_play: str = field(default=os.path.expanduser("~/.alarms/alarm-lofi.mp3")) enabled: bool = field(default=True) snooze: Snooze = field(default_factory=Snooze) metadata: Metadata = field(default_factory=Metadata) id: Optional[int] = field(default=None) def validate(self) -> bool: """Validate complete alarm configuration""" logger.debug(f"Starting validation for alarm: {self.name}") # Validate name if not isinstance(self.name, str) or len(self.name.strip()) == 0: logger.error(f"Invalid alarm name: '{self.name}'. Must be non-empty string") return False # Validate time format time_pattern = re.compile(r'^([0-1]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$') if not time_pattern.match(self.time): logger.error(f"Invalid time format: '{self.time}'. Must match HH:MM:SS") return False # Validate enabled flag if not isinstance(self.enabled, bool): logger.error(f"Enabled must be boolean, got {type(self.enabled)}") return False # Create default alarms directory if it doesn't exist default_alarms_dir = os.path.expanduser("~/.alarms") os.makedirs(default_alarms_dir, exist_ok=True) # Validate audio file if self.file_to_play != os.path.expanduser("~/.alarms/alarm-lofi.mp3") and not os.path.exists(self.file_to_play): logger.error(f"Audio file not found: '{self.file_to_play}'") return False # Validate repeat rule if not isinstance(self.repeat_rule, RepeatRule): logger.error(f"Invalid repeat_rule type: {type(self.repeat_rule)}") return False if not self.repeat_rule.validate(): logger.error("Repeat rule validation failed") return False # Validate snooze if not isinstance(self.snooze, Snooze): logger.error(f"Invalid snooze type: {type(self.snooze)}") return False if not self.snooze.validate(): logger.error("Snooze validation failed") return False # Validate metadata if not isinstance(self.metadata, Metadata): logger.error(f"Invalid metadata type: {type(self.metadata)}") return False if not self.metadata.validate(): logger.error("Metadata validation failed") return False logger.debug(f"Alarm validation passed: {self.name}") return True