from datetime import datetime, timedelta from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, session from flask_login import login_user, logout_user, login_required, current_user from werkzeug.security import check_password_hash from sqlalchemy import func, desc import uuid import json from app import db from app.models import User, Incident bp = Blueprint('main', __name__) @bp.route('/health') def health_check(): """Health check endpoint voor Kubernetes""" try: # Check database connectivity db.session.execute('SELECT 1') return jsonify({ 'status': 'healthy', 'timestamp': datetime.utcnow().isoformat(), 'database': 'connected' }), 200 except Exception as e: return jsonify({ 'status': 'unhealthy', 'timestamp': datetime.utcnow().isoformat(), 'database': 'disconnected', 'error': str(e) }), 503 @bp.route('/') def index(): """Homepage met incident invoer""" return render_template('index.html') @bp.route('/add-incident', methods=['GET', 'POST']) def add_incident(): """Incident toevoegen (voor geregistreerde gebruikers)""" if request.method == 'POST': try: severity_str = request.form.get('severity') notes = request.form.get('notes', '').strip() incident_time = request.form.get('incident_time') print(f"DEBUG add_incident - Severity: {severity_str}, Time: {incident_time}") # Validatie if not severity_str: flash('Selecteer een Snauw-index', 'error') return redirect(url_for('main.add_incident')) severity = int(severity_str) if not (1 <= severity <= 5): flash('Snauw-index moet tussen 1 en 5 zijn', 'error') return redirect(url_for('main.add_incident')) # Parse timestamp if incident_time: try: # Handle different datetime formats if 'T' in incident_time: timestamp = datetime.fromisoformat(incident_time.replace('Z', '+00:00')) else: timestamp = datetime.strptime(incident_time, '%Y-%m-%d %H:%M:%S') except ValueError as dt_error: print(f"DEBUG DateTime parse error: {dt_error}") timestamp = datetime.utcnow() else: timestamp = datetime.utcnow() # Maak incident incident = Incident( user_id=current_user.id if current_user.is_authenticated else None, timestamp=timestamp, severity=severity, notes=notes if notes else None, session_id=get_or_create_session_id() if not current_user.is_authenticated else None ) db.session.add(incident) db.session.commit() flash('✅ Incident succesvol opgeslagen!', 'success') return redirect(url_for('main.statistics')) except ValueError as e: print(f"DEBUG ValueError in add_incident: {e}") flash('Ongeldige invoer. Controleer je gegevens.', 'error') except Exception as e: print(f"DEBUG Exception in add_incident: {e}") flash('Er is een fout opgetreden bij het opslaan.', 'error') db.session.rollback() return render_template('add_incident.html') @bp.route('/api/incidents', methods=['POST']) def api_add_incident(): """API endpoint voor het toevoegen van incidenten (voor AJAX)""" try: data = request.get_json() severity = int(data.get('severity')) notes = data.get('notes', '').strip() timestamp_str = data.get('timestamp') if not (1 <= severity <= 5): return jsonify({'error': 'Snauw-index moet tussen 1 en 5 zijn'}), 400 # Parse timestamp if timestamp_str: timestamp = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00')) else: timestamp = datetime.utcnow() # Maak incident incident = Incident( user_id=current_user.id if current_user.is_authenticated else None, timestamp=timestamp, severity=severity, notes=notes if notes else None, session_id=get_or_create_session_id() if not current_user.is_authenticated else None ) db.session.add(incident) db.session.commit() return jsonify({ 'success': True, 'message': 'Incident succesvol opgeslagen!', 'incident': incident.to_dict() }) except ValueError: return jsonify({'error': 'Ongeldige invoer'}), 400 except Exception as e: db.session.rollback() return jsonify({'error': 'Er is een fout opgetreden'}), 500 @bp.route('/statistics') def statistics(): """Statistieken pagina""" # Voor ingelogde gebruikers if current_user.is_authenticated: incidents = Incident.query.filter_by(user_id=current_user.id).order_by(desc(Incident.timestamp)).all() else: # Voor anonieme gebruikers - via session_id session_id = get_or_create_session_id() incidents = Incident.query.filter_by(session_id=session_id).order_by(desc(Incident.timestamp)).all() # Bereken statistieken stats = calculate_statistics(incidents) return render_template('statistics.html', incidents=incidents, stats=stats) @bp.route('/api/statistics') def api_statistics(): """API endpoint voor statistieken (voor AJAX updates)""" if current_user.is_authenticated: incidents = Incident.query.filter_by(user_id=current_user.id).all() else: session_id = get_or_create_session_id() incidents = Incident.query.filter_by(session_id=session_id).all() stats = calculate_statistics(incidents) return jsonify({ 'stats': stats, 'incidents': [incident.to_dict() for incident in incidents] }) @bp.route('/register', methods=['GET', 'POST']) def register(): """Gebruiker registratie""" if current_user.is_authenticated: return redirect(url_for('main.index')) if request.method == 'POST': email = request.form.get('email', '').strip().lower() password = request.form.get('password') password_confirm = request.form.get('password_confirm') # Debug informatie print(f"DEBUG Register - Email: {email}, Password len: {len(password) if password else 0}") # Validatie if not email or '@' not in email or '.' not in email: flash('Voer een geldig e-mailadres in', 'error') return render_template('register.html') if not password or len(password) < 6: flash('Wachtwoord moet minstens 6 karakters lang zijn', 'error') return render_template('register.html') if password != password_confirm: flash('Wachtwoorden komen niet overeen', 'error') return render_template('register.html') # Check of gebruiker al bestaat if User.query.filter_by(email=email).first(): flash('Er bestaat al een account met dit e-mailadres', 'error') return render_template('register.html') try: # Maak nieuwe gebruiker user = User(email=email) user.set_password(password) db.session.add(user) db.session.commit() # Probeer data migratie van anonieme gebruiker migrate_anonymous_data(user) # Log in de gebruiker login_user(user) flash('✅ Account succesvol aangemaakt en ingelogd!', 'success') return redirect(url_for('main.index')) except Exception as e: print(f"DEBUG Register error: {e}") db.session.rollback() flash('Er is een fout opgetreden bij het maken van je account', 'error') return render_template('register.html') return render_template('register.html') @bp.route('/login', methods=['GET', 'POST']) def login(): """Gebruiker login""" if current_user.is_authenticated: return redirect(url_for('main.index')) if request.method == 'POST': email = request.form.get('email', '').strip().lower() password = request.form.get('password') if not email or not password: flash('Voer e-mail en wachtwoord in', 'error') return render_template('login.html') user = User.query.filter_by(email=email).first() if user and user.check_password(password): login_user(user) # Probeer data migratie van anonieme gebruiker migrate_anonymous_data(user) flash('✅ Succesvol ingelogd!', 'success') next_page = request.args.get('next') return redirect(next_page) if next_page else redirect(url_for('main.index')) else: flash('Ongeldig e-mailadres of wachtwoord', 'error') return render_template('login.html') @bp.route('/logout') @login_required def logout(): """Gebruiker logout""" logout_user() flash('Je bent uitgelogd', 'info') return redirect(url_for('main.index')) @bp.route('/migrate-data', methods=['POST']) def migrate_data(): """Data migratie van anonieme naar geregistreerde gebruiker""" if not current_user.is_authenticated: return jsonify({'error': 'Inloggen vereist'}), 401 try: data = request.get_json() anonymous_incidents = data.get('incidents', []) migrated_count = 0 for incident_data in anonymous_incidents: # Controleer of incident al bestaat (op basis van timestamp en severity) timestamp = datetime.fromisoformat(incident_data['timestamp'].replace('Z', '+00:00')) existing = Incident.query.filter_by( user_id=current_user.id, timestamp=timestamp, severity=incident_data['severity'] ).first() if not existing: incident = Incident( user_id=current_user.id, timestamp=timestamp, severity=incident_data['severity'], notes=incident_data.get('notes') ) db.session.add(incident) migrated_count += 1 db.session.commit() return jsonify({ 'success': True, 'migrated_count': migrated_count, 'message': f'{migrated_count} incidenten succesvol gemigreerd!' }) except Exception as e: db.session.rollback() return jsonify({'error': 'Fout bij data migratie'}), 500 @bp.route('/export-data') def export_data(): """GDPR data export""" if current_user.is_authenticated: incidents = Incident.query.filter_by(user_id=current_user.id).all() data = { 'user': { 'email': current_user.email, 'created_at': current_user.created_at.isoformat() }, 'incidents': [incident.to_dict() for incident in incidents], 'export_date': datetime.utcnow().isoformat(), 'total_incidents': len(incidents) } else: session_id = get_or_create_session_id() incidents = Incident.query.filter_by(session_id=session_id).all() data = { 'session_id': session_id, 'incidents': [incident.to_dict() for incident in incidents], 'export_date': datetime.utcnow().isoformat(), 'total_incidents': len(incidents), 'note': 'Anonieme gebruiker data' } return jsonify(data) @bp.route('/delete-account', methods=['POST']) @login_required def delete_account(): """GDPR account verwijdering""" if request.form.get('confirm') != 'DELETE': flash('Type "DELETE" om je account te bevestigen', 'error') return redirect(url_for('main.statistics')) try: # Verwijder alle incidenten van de gebruiker Incident.query.filter_by(user_id=current_user.id).delete() # Verwijder de gebruiker user_email = current_user.email db.session.delete(current_user) db.session.commit() logout_user() flash(f'Account {user_email} en alle gegevens zijn permanent verwijderd', 'info') return redirect(url_for('main.index')) except Exception as e: db.session.rollback() flash('Fout bij het verwijderen van account', 'error') return redirect(url_for('main.statistics')) @bp.route('/privacy') def privacy(): """Privacy beleid pagina""" return render_template('privacy.html') # Helper functions def get_or_create_session_id(): """Haal session ID op of maak een nieuwe voor anonieme gebruikers""" if 'session_id' not in session: session['session_id'] = str(uuid.uuid4()) return session['session_id'] def migrate_anonymous_data(user): """Probeer data van anonieme gebruiker naar geregistreerde gebruiker te migreren""" if 'session_id' in session: session_id = session['session_id'] # Vind incidenten van anonieme sessie anonymous_incidents = Incident.query.filter_by(session_id=session_id).all() if anonymous_incidents: # Migreer naar gebruiker for incident in anonymous_incidents: incident.user_id = user.id incident.session_id = None db.session.commit() flash(f'✨ {len(anonymous_incidents)} incidenten zijn gemigreerd naar je account!', 'success') def calculate_statistics(incidents): """Bereken statistieken voor incidents""" if not incidents: return { 'total_incidents': 0, 'avg_severity': 0, 'incidents_today': 0, 'incidents_this_week': 0, 'incidents_this_month': 0, 'most_severe': 0, 'trend_data': [] } now = datetime.utcnow() today = now.date() week_ago = now - timedelta(days=7) month_ago = now - timedelta(days=30) # Basis statistieken total_incidents = len(incidents) severities = [inc.severity for inc in incidents] avg_severity = sum(severities) / len(severities) if severities else 0 most_severe = max(severities) if severities else 0 # Tijd-gebaseerde counts incidents_today = len([inc for inc in incidents if inc.timestamp.date() == today]) incidents_this_week = len([inc for inc in incidents if inc.timestamp >= week_ago]) incidents_this_month = len([inc for inc in incidents if inc.timestamp >= month_ago]) # Trend data (laatste 7 dagen) trend_data = [] for i in range(7): date = now - timedelta(days=i) day_incidents = [inc for inc in incidents if inc.timestamp.date() == date.date()] trend_data.append({ 'date': date.strftime('%m/%d'), 'count': len(day_incidents), 'avg_severity': sum(inc.severity for inc in day_incidents) / len(day_incidents) if day_incidents else 0 }) trend_data.reverse() # Chronologische volgorde return { 'total_incidents': total_incidents, 'avg_severity': round(avg_severity, 1), 'incidents_today': incidents_today, 'incidents_this_week': incidents_this_week, 'incidents_this_month': incidents_this_month, 'most_severe': most_severe, 'trend_data': trend_data }