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.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.services.cockpit import build_dashboard_cockpit from apps.profiles.models import SearchProfile from apps.sources.services.health import collect_source_health from .health import readiness from .services.automation import build_automation_cockpit 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.update(build_automation_cockpit()) context["source_health"] = collect_source_health() return context