several updates to functionality

This commit is contained in:
2026-02-02 13:16:18 +01:00
parent 1f3ba037d6
commit 3ac987ac34
19 changed files with 1808 additions and 135 deletions

212
validation.py Normal file
View File

@@ -0,0 +1,212 @@
from datetime import datetime, time
from typing import Any, Optional
DATE_FORMAT = "%Y-%m-%d"
TIME_FORMAT = "%H:%M"
def validate_config(config: dict[str, Any]) -> dict[str, Any]:
errors: list[str] = []
if not isinstance(config, dict):
raise ValueError("config skal være et JSON-objekt")
start_date_value = config.get("start_date")
start_time_value = config.get("start_time")
if not isinstance(start_date_value, str):
errors.append("start_date skal være en streng i format YYYY-MM-DD")
start_date_obj = None
else:
try:
start_date_obj = datetime.strptime(start_date_value, DATE_FORMAT).date()
except ValueError:
errors.append("start_date skal være i format YYYY-MM-DD")
start_date_obj = None
if not isinstance(start_time_value, str):
errors.append("start_time skal være en streng i format HH:MM")
start_time_obj = None
else:
try:
t_hour, t_min = map(int, start_time_value.split(":"))
start_time_obj = time(t_hour, t_min)
except (ValueError, TypeError):
errors.append("start_time skal være i format HH:MM")
start_time_obj = None
blocked_weeks = config.get("blocked_weeks", [])
if blocked_weeks is None:
blocked_weeks = []
if not isinstance(blocked_weeks, list):
errors.append("blocked_weeks skal være en liste af heltal")
else:
invalid_weeks = [
week for week in blocked_weeks
if not isinstance(week, int) or week < 1 or week > 53
]
if invalid_weeks:
errors.append(
"blocked_weeks skal indeholde heltal mellem 1 og 53: "
f"{invalid_weeks}"
)
blocked_dates = config.get("blocked_dates", [])
if blocked_dates is None:
blocked_dates = []
if not isinstance(blocked_dates, list):
errors.append("blocked_dates skal være en liste af dato-strenge")
else:
invalid_dates = []
blocked_full_dates: list[str] = []
blocked_month_days: list[str] = []
for date_value in blocked_dates:
if not isinstance(date_value, str):
invalid_dates.append(date_value)
continue
try:
date_obj = datetime.strptime(date_value, DATE_FORMAT).date()
except ValueError:
month_day = _parse_month_day(date_value)
if month_day:
blocked_month_days.append(month_day)
else:
invalid_dates.append(date_value)
else:
blocked_full_dates.append(date_obj.strftime(DATE_FORMAT))
if invalid_dates:
errors.append(
"blocked_dates skal være i format YYYY-MM-DD eller MM-DD: "
f"{invalid_dates}"
)
if "skip_day_after_ascension" in config and not isinstance(
config["skip_day_after_ascension"], bool
):
errors.append("skip_day_after_ascension skal være true/false")
for key in ("repo_url", "organizer_email", "uid_namespace"):
if key in config and config[key] is not None and not isinstance(config[key], str):
errors.append(f"{key} skal være en streng")
if errors:
raise ValueError("Konfigurationsfejl:\n- " + "\n- ".join(errors))
config["start_date_obj"] = start_date_obj
config["start_time_obj"] = start_time_obj
config["blocked_weeks"] = blocked_weeks
config["blocked_dates"] = blocked_full_dates
config["blocked_month_days"] = blocked_month_days
return config
def validate_story_data(story_data: Any) -> dict[str, Any]:
errors: list[str] = []
if not isinstance(story_data, dict):
raise ValueError("Story JSON skal være et objekt med meta og events")
meta = story_data.get("meta")
if not isinstance(meta, dict):
errors.append("meta skal være et objekt")
else:
name = meta.get("name")
if not isinstance(name, str) or not name.strip():
errors.append("meta.name skal være en ikke-tom streng")
for key in ("id", "theme_color", "text_color", "bg_color", "font", "log_prefix"):
value = meta.get(key)
if value is not None and not isinstance(value, str):
errors.append(f"meta.{key} skal være en streng hvis den er sat")
events = story_data.get("events")
if not isinstance(events, list):
errors.append("events skal være en liste")
else:
for idx, event_entry in enumerate(events, start=1):
if not isinstance(event_entry, dict):
errors.append(f"events[{idx}] skal være et objekt")
continue
title = event_entry.get("title")
if not isinstance(title, str) or not title.strip():
errors.append(f"events[{idx}].title skal være en ikke-tom streng")
story = event_entry.get("story")
if not isinstance(story, str) or not story.strip():
errors.append(f"events[{idx}].story skal være en ikke-tom streng")
hint = event_entry.get("hint")
if hint is not None and not isinstance(hint, str):
errors.append(f"events[{idx}].hint skal være en streng hvis den er sat")
event_id = event_entry.get("id")
if event_id is not None and not isinstance(event_id, str):
errors.append(f"events[{idx}].id skal være en streng hvis den er sat")
uid = event_entry.get("uid")
if uid is not None and not isinstance(uid, str):
errors.append(f"events[{idx}].uid skal være en streng hvis den er sat")
if errors:
raise ValueError("Historie-fejl:\n- " + "\n- ".join(errors))
return story_data
def _parse_month_day(value: str) -> Optional[str]:
parts = value.split("-")
if len(parts) != 2:
return None
if not all(part.isdigit() for part in parts):
return None
month = int(parts[0])
day = int(parts[1])
try:
datetime(2000, month, day)
except ValueError:
return None
return f"{month:02d}-{day:02d}"
def _events_missing_ids(events_data: list[dict[str, Any]]) -> list[int]:
missing: list[int] = []
for idx, event_entry in enumerate(events_data, start=1):
uid_value = event_entry.get("uid")
if isinstance(uid_value, str):
uid_value = uid_value.strip() or None
if not event_entry.get("id") and not uid_value:
missing.append(idx)
return missing
def validate_uid_mapping(
events_data: list[dict[str, Any]],
duplicate_titles: set[str],
log_totals: set[int],
) -> bool:
ambiguous_reasons: list[str] = []
if duplicate_titles:
titles_list = ", ".join(sorted(duplicate_titles))
ambiguous_reasons.append(f"duplikerede titler i eksisterende .ics ({titles_list})")
if len(log_totals) > 1:
totals_list = ", ".join(str(total) for total in sorted(log_totals))
ambiguous_reasons.append(f"flere log-totaler i eksisterende .ics ({totals_list})")
elif len(log_totals) == 1:
total = next(iter(log_totals))
if total != len(events_data):
ambiguous_reasons.append(
"log-total matcher ikke antal events i story "
f"({total} != {len(events_data)})"
)
if not ambiguous_reasons:
return True
missing = _events_missing_ids(events_data)
if missing:
missing_list = ", ".join(str(index) for index in missing)
raise ValueError(
"Ambivalent UID-mapping: "
+ "; ".join(ambiguous_reasons)
+ ". Tilfoej events[].id eller events[].uid for events: "
+ missing_list
)
return False