from __future__ import annotations from datetime import datetime, timezone from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render from django.views import View from core.models import ( ContactAvailabilityEvent, ContactAvailabilitySettings, ContactAvailabilitySpan, Person, ) def _to_int(value, default=0): try: return int(value) except Exception: return int(default) def _to_bool(value, default=False): if value is None: return bool(default) text = str(value).strip().lower() if text in {"1", "true", "yes", "on", "y"}: return True if text in {"0", "false", "no", "off", "n"}: return False return bool(default) def _iso_to_ms(value: str) -> int: raw = str(value or "").strip() if not raw: return 0 try: dt = datetime.fromisoformat(raw) if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return int(dt.timestamp() * 1000) except Exception: return 0 class AvailabilitySettingsPage(LoginRequiredMixin, View): template_name = "pages/availability-settings.html" def _settings(self, request): row, _ = ContactAvailabilitySettings.objects.get_or_create(user=request.user) return row def post(self, request): row = self._settings(request) row.enabled = _to_bool(request.POST.get("enabled"), row.enabled) row.show_in_chat = _to_bool(request.POST.get("show_in_chat"), row.show_in_chat) row.show_in_groups = _to_bool( request.POST.get("show_in_groups"), row.show_in_groups ) row.inference_enabled = _to_bool( request.POST.get("inference_enabled"), row.inference_enabled ) row.retention_days = max(1, _to_int(request.POST.get("retention_days"), 90)) row.fade_threshold_seconds = max( 30, _to_int(request.POST.get("fade_threshold_seconds"), 900) ) row.save( update_fields=[ "enabled", "show_in_chat", "show_in_groups", "inference_enabled", "retention_days", "fade_threshold_seconds", "updated_at", ] ) return self.get(request) def get(self, request): settings_row = self._settings(request) person_id = str(request.GET.get("person") or "").strip() service = str(request.GET.get("service") or "").strip().lower() state = str(request.GET.get("state") or "").strip().lower() source_kind = str(request.GET.get("source_kind") or "").strip().lower() start_ts = _iso_to_ms(request.GET.get("start")) end_ts = _iso_to_ms(request.GET.get("end")) if end_ts <= 0: end_ts = int(datetime.now(tz=timezone.utc).timestamp() * 1000) if start_ts <= 0: start_ts = max(0, end_ts - (14 * 24 * 60 * 60 * 1000)) events_qs = ContactAvailabilityEvent.objects.filter(user=request.user) spans_qs = ContactAvailabilitySpan.objects.filter(user=request.user) if person_id: events_qs = events_qs.filter(person_id=person_id) spans_qs = spans_qs.filter(person_id=person_id) if service: events_qs = events_qs.filter(service=service) spans_qs = spans_qs.filter(service=service) if state: events_qs = events_qs.filter(availability_state=state) spans_qs = spans_qs.filter(state=state) if source_kind: events_qs = events_qs.filter(source_kind=source_kind) events_qs = events_qs.filter(ts__gte=start_ts, ts__lte=end_ts) spans_qs = spans_qs.filter(start_ts__lte=end_ts, end_ts__gte=start_ts) events = list( events_qs.select_related("person", "person_identifier").order_by("-ts")[:500] ) spans = list( spans_qs.select_related("person", "person_identifier").order_by("-end_ts")[:500] ) people = list(Person.objects.filter(user=request.user).order_by("name")) context = { "settings_row": settings_row, "people": people, "events": events, "spans": spans, "filters": { "person": person_id, "service": service, "state": state, "source_kind": source_kind, "start": request.GET.get("start") or "", "end": request.GET.get("end") or "", }, "service_choices": ["signal", "whatsapp", "xmpp", "instagram", "web"], "state_choices": ["available", "fading", "unavailable", "unknown"], "source_kind_choices": [ "native_presence", "read_receipt", "typing_start", "typing_stop", "message_in", "message_out", "inferred_timeout", ], } return render(request, self.template_name, context)