from __future__ import annotations from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.views import LoginView from django.db.models import Count from django.http import JsonResponse from django.views.generic import TemplateView from apps.core.rate_limit import clear_rate_limit, is_rate_limited, register_rate_limit_failure from apps.jobs.models import JobPosting from apps.jobs.services.cockpit import build_dashboard_cockpit from apps.profiles.models import SearchProfile from apps.sources.models import Source from apps.sources.services.health import collect_source_health from .health import readiness class SecurityAwareLoginView(LoginView): template_name = "registration/login.html" @staticmethod def _remaining_minutes(seconds: int) -> str: minutes = max(1, (seconds + 59) // 60) return f"{minutes} minuut" if minutes == 1 else f"{minutes} minuten" def dispatch(self, request, *args, **kwargs): if request.method == "POST": state = is_rate_limited( request, namespace="login", max_attempts=settings.AUTH_LOGIN_RATE_LIMIT_MAX_ATTEMPTS, window_seconds=settings.AUTH_LOGIN_RATE_LIMIT_WINDOW_SECONDS, block_seconds=settings.AUTH_LOGIN_RATE_LIMIT_BLOCK_SECONDS, ) if state.is_blocked: messages.error( request, ( "Te veel inlogpogingen op dit account. Wacht " f"{self._remaining_minutes(state.remaining_seconds)} en probeer daarna " "opnieuw." ), ) return self.form_invalid(self.get_form()) return super().dispatch(request, *args, **kwargs) def form_invalid(self, form): state = register_rate_limit_failure( self.request, namespace="login", max_attempts=settings.AUTH_LOGIN_RATE_LIMIT_MAX_ATTEMPTS, window_seconds=settings.AUTH_LOGIN_RATE_LIMIT_WINDOW_SECONDS, block_seconds=settings.AUTH_LOGIN_RATE_LIMIT_BLOCK_SECONDS, ) if state.is_blocked: messages.error( self.request, ( "Te veel inlogpogingen. Wacht " f"{self._remaining_minutes(state.remaining_seconds)} en probeer daarna opnieuw." ), ) return super().form_invalid(form) def form_valid(self, form): clear_rate_limit(self.request, namespace="login") return super().form_valid(form) def health_live(request): return JsonResponse({"ok": True, "service": "vacatureradar"}) def health_ready(request): result = readiness() return JsonResponse(result.to_dict(), status=200 if result.ok else 503) class TodayView(LoginRequiredMixin, TemplateView): template_name = "dashboard/today.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) profile = SearchProfile.objects.filter(user=self.request.user, is_active=True).first() context.update(build_dashboard_cockpit(self.request.user, profile)) context["active_profile"] = profile return context class SystemStatusView(LoginRequiredMixin, TemplateView): template_name = "system/status.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["health"] = readiness() context["source_summary"] = Source.objects.values("status").annotate(total=Count("id")) context["job_summary"] = JobPosting.objects.values("status").annotate(total=Count("id")) context["source_health"] = collect_source_health() return context