import json import logging import os from datetime import datetime from typing import Optional from backend.models import RuleSet, AccessRule, DayOfWeek from backend.settings import get_settings, Settings logger = logging.getLogger(__name__) class RuleManager: def __init__(self, settings: Optional[Settings] = None): self.settings = settings or get_settings() self.rules_file = self.settings.rules_file def load_rules(self) -> RuleSet: if not os.path.exists(self.rules_file): return RuleSet() try: with open(self.rules_file, "r", encoding="utf-8") as f: data = json.load(f) return RuleSet(**data) except (json.JSONDecodeError, OSError) as e: logger.error("Failed to load rules from %s: %s", self.rules_file, e) return RuleSet() def save_rules(self, rules: RuleSet) -> None: try: os.makedirs(os.path.dirname(self.rules_file), exist_ok=True) with open(self.rules_file, "w", encoding="utf-8") as f: f.write(rules.model_dump_json(indent=2)) except OSError as e: logger.error("Failed to save rules to %s: %s", self.rules_file, e) raise def get_rule(self, user: str) -> Optional[AccessRule]: rules = self.load_rules() return rules.rules.get(user) def set_rule(self, rule: AccessRule) -> None: rules = self.load_rules() rules.rules[rule.user] = rule self.save_rules(rules) def delete_rule(self, user: str) -> None: rules = self.load_rules() if user in rules.rules: del rules.rules[user] self.save_rules(rules) def is_login_allowed(self, user: str, now: Optional[datetime] = None) -> bool: """ Check if user is allowed to login at the given time (or now). Returns True if no rule exists (or disabled) or if time is within an allowed window. Returns False if a rule exists and current time is outside all allowed windows. """ rule = self.get_rule(user) if not rule or not rule.enabled: # No rule or rule disabled -> Default Allow return True if not rule.allowed_windows: # Rule enabled but no windows -> Deny All return False if now is None: now = datetime.now() # Map weekday to Enum weekday_map = { 0: DayOfWeek.MONDAY, 1: DayOfWeek.TUESDAY, 2: DayOfWeek.WEDNESDAY, 3: DayOfWeek.THURSDAY, 4: DayOfWeek.FRIDAY, 5: DayOfWeek.SATURDAY, 6: DayOfWeek.SUNDAY, } current_day = weekday_map[now.weekday()] current_time_str = now.strftime("%H:%M") for window in rule.allowed_windows: if current_day in window.days: # Handle simple range start <= now <= end # (Overnight windows assumed to be split by user into two windows) if window.start_time <= current_time_str <= window.end_time: return True return False