from flask import Flask, request, jsonify, session, render_template_string, redirect import os import json import random import string import re import hashlib from datetime import date, datetime, timedelta from functools import wraps app = Flask(__name__) app.secret_key = os.environ.get('SESSION_SECRET', 'dev-secret') # === BENUTZERVERWALTUNG === default_users = [ { 'id': 1, 'username': 'admin', 'password_hash': hashlib.sha256('changeme'.encode()).hexdigest(), 'name': 'Administrator', 'email': 'admin@restaurant.de', 'role': 'admin', 'is_active': True, 'created_at': datetime.now().isoformat(), 'last_login': None, 'force_password_change': False # Nach erstmaligem Login Passwort ändern } ] users = default_users.copy() ROLE_PERMISSIONS = { 'admin': ['dashboard', 'reservations', 'rooms', 'tables', 'templates', 'smtp', 'users', 'reports', 'settings', 'change_password'], 'manager': ['dashboard', 'reservations', 'rooms', 'tables', 'templates', 'reports', 'change_password'], 'staff': ['dashboard', 'reservations', 'rooms', 'change_password'] } def hash_password(password): return hashlib.sha256(password.encode()).hexdigest() def check_permission(user_role, permission): return permission in ROLE_PERMISSIONS.get(user_role, []) def require_permission(permission): def decorator(f): @wraps(f) def decorated(*args, **kwargs): if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 user = next((u for u in users if u['id'] == session['user_id']), None) if not user or not user.get('is_active', False): return jsonify({'error': 'Unauthorized'}), 401 if not check_permission(user['role'], permission): return jsonify({'error': 'Forbidden'}), 403 return f(*args, **kwargs) return decorated return decorator # SMTP-Konfiguration smtp_config = { 'host': os.environ.get('SMTP_HOST', ''), 'port': int(os.environ.get('SMTP_PORT', 587)), 'user': os.environ.get('SMTP_USER', ''), 'password': os.environ.get('SMTP_PASS', ''), 'from': os.environ.get('SMTP_FROM', 'reservierung@restaurant.de') } email_templates = { 'confirmation': """Sehr geehrte/r {name}, vielen Dank für Ihre Reservierung bei uns. Buchungsdetails: - Buchungsnummer: {booking_id} - Datum: {date} - Uhrzeit: {time} - Personen: {guests} - Tische: {tables} Wir freuen uns auf Ihren Besuch! Mit freundlichen Grüßen Ihr Restaurant-Team """, 'email_reply': """Sehr geehrte/r {name}, vielen Dank für Ihre E-Mail. Wir haben Ihre Reservierungsanfrage erhalten. {parsed_info} Ihre Buchungsnummer lautet: {booking_id} Bei Rückfragen erreichen Sie uns telefonisch oder per E-Mail. Mit freundlichen Grüßen Ihr Restaurant-Team """, 'user_welcome': """Sehr geehrte/r {name}, Ihr Account für das Reservierungssystem wurde erstellt. Zugangsdaten: - Benutzername: {username} - Passwort: {password} - Rolle: {role} Bitte ändern Sie Ihr Passwort nach dem ersten Login. Mit freundlichen Grüßen Ihr Restaurant-Team """, 'password_changed': """Sehr geehrte/r {name}, Ihr Passwort wurde erfolgreich geändert. Falls Sie diese Änderung nicht vorgenommen haben, kontaktieren Sie bitte umgehend den Administrator. Mit freundlichen Grüßen Ihr Restaurant-Team """ } # Datenstruktur rooms = [ { 'id': 1, 'name': 'Hauptraum', 'description': 'Großer Saal mit Fensterfront', 'tables': [ {'id': 1, 'name': 'Tisch 1', 'seats': 4, 'shape': 'circle'}, {'id': 2, 'name': 'Tisch 2', 'seats': 2, 'shape': 'circle'}, {'id': 3, 'name': 'Tisch 3', 'seats': 6, 'shape': 'rect'}, {'id': 4, 'name': 'Tisch 4', 'seats': 4, 'shape': 'rect'}, ] }, { 'id': 2, 'name': 'Nebenraum', 'description': 'Gemütlicher kleiner Raum', 'tables': [ {'id': 5, 'name': 'Tisch A', 'seats': 4, 'shape': 'circle'}, {'id': 6, 'name': 'Tisch B', 'seats': 4, 'shape': 'circle'}, ] }, { 'id': 3, 'name': 'Terrasse', 'description': 'Draußen unter dem Vordach', 'tables': [ {'id': 7, 'name': 'Tisch T1', 'seats': 4, 'shape': 'rect'}, {'id': 8, 'name': 'Tisch T2', 'seats': 4, 'shape': 'rect'}, {'id': 9, 'name': 'Tisch T3', 'seats': 6, 'shape': 'rect'}, ] } ] reservations = [] def generate_booking_id(): while True: booking_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8)) if not any(r.get('booking_id') == booking_id for r in reservations): return booking_id def get_table(table_id): for room in rooms: for table in room['tables']: if table['id'] == table_id: return table, room return None, None def get_available_tables(date_str, time_str): available = [] for room in rooms: for table in room['tables']: table_copy = table.copy() table_copy['room_id'] = room['id'] table_copy['room_name'] = room['name'] available.append(table_copy) return available def is_table_available(table_id, date_str, time_str): for res in reservations: if res['date'] == date_str and res['time'] == time_str: if table_id in res.get('table_ids', []): return False return True def send_email_smtp(to_email, subject, body): try: import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart if not smtp_config['host']: print(f"[SMTP] Kein SMTP konfiguriert. E-Mail würde an {to_email}:") return True msg = MIMEMultipart() msg['From'] = smtp_config['from'] msg['To'] = to_email msg['Subject'] = subject msg.attach(MIMEText(body, 'plain', 'utf-8')) server = smtplib.SMTP(smtp_config['host'], smtp_config['port']) server.starttls() server.login(smtp_config['user'], smtp_config['password']) server.send_message(msg) server.quit() return True except Exception as e: print(f"[SMTP] Fehler: {e}") return False # === HTML TEMPLATES === PUBLIC_RESERVATION_HTML = ''' Reservierung - Restaurant

🍽️ Tischreservierung

Reservieren Sie Ihren Tisch einfach online

📅 Wann möchten Sie reservieren?

👥 Wie viele Personen?

🪑 Welcher Raum?

👤 Ihre Kontaktdaten

🤖 Sicherheitsprüfung

''' # Admin Login mit Passwort-Änderung-Aufforderung ADMIN_LOGIN_HTML = ''' Admin Login - Reservierung

🔐 Admin Login

Reservierungssystem

{% if force_password_change %}
⚠️ Sicherheitshinweis: Bitte ändern Sie Ihr Passwort nach dem Login.
{% endif %}
''' # Passwort-Änderung Seite CHANGE_PASSWORD_HTML = ''' Passwort ändern - Reservierung

🔐 Passwort ändern

Sichern Sie Ihr Konto

{% if force_change %}
⚠️ Sicherheitshinweis: Sie müssen Ihr Passwort ändern, bevor Sie fortfahren können.
{% endif %}
''' ADMIN_DASHBOARD_HTML = ''' Admin Dashboard - Reservierung

📊 Reservierung Admin

Role
0
Gesamtbuchungen
0
Heute
0
Diese Woche
0
Benutzer

🔍 Schnellsuche

''' # === API ROUTES === @app.route('/api/login', methods=['POST']) def login(): data = request.get_json() or {} username = data.get('username', '').lower() password = data.get('password', '') password_hash = hash_password(password) user = next((u for u in users if u['username'].lower() == username and u['password_hash'] == password_hash and u.get('is_active', False)), None) if user: session['user_id'] = user['id'] user['last_login'] = datetime.now().isoformat() return jsonify({ 'status': 'ok', 'user': {'id': user['id'], 'name': user['name'], 'role': user['role']}, 'force_password_change': user.get('force_password_change', False) }) return jsonify({'error': 'Invalid username or password'}), 401 @app.route('/api/logout', methods=['POST']) def logout(): session.pop('user_id', None) return jsonify({'status': 'ok'}) # Passwort ändern @app.route('/api/change-password', methods=['GET', 'POST']) def change_password(): if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 user = next((u for u in users if u['id'] == session['user_id']), None) if not user: return jsonify({'error': 'User not found'}), 404 if request.method == 'GET': return jsonify({ 'force_change': user.get('force_password_change', False) }) # POST: Passwort ändern data = request.get_json() or {} current_password = data.get('current_password', '') new_password = data.get('new_password', '') # Validate current password if hash_password(current_password) != user['password_hash']: return jsonify({'error': 'Aktuelles Passwort ist falsch'}), 400 # Validate new password if len(new_password) < 8: return jsonify({'error': 'Neues Passwort muss mindestens 8 Zeichen haben'}), 400 # Update password user['password_hash'] = hash_password(new_password) user['force_password_change'] = False # Send confirmation email try: body = email_templates['password_changed'].format(name=user['name']) send_email_smtp(user['email'], 'Ihr Passwort wurde geändert', body) except Exception as e: print(f"Password change email error: {e}") return jsonify({'status': 'ok'}) @app.route('/api/me') def get_current_user(): if 'user_id' not in session: return jsonify({'error': 'Not logged in'}), 401 user = next((u for u in users if u['id'] == session['user_id']), None) if not user: return jsonify({'error': 'User not found'}), 404 return jsonify({ 'user': {'id': user['id'], 'name': user['name'], 'role': user['role']}, 'permissions': ROLE_PERMISSIONS.get(user['role'], []) }) # Public API @app.route('/api/public/tables') def public_get_tables(): date_str = request.args.get('date', str(date.today())) time_str = request.args.get('time', '19:00') available = get_available_tables(date_str, time_str) return jsonify({'tables': available}) @app.route('/api/public/reservations', methods=['POST']) def public_create_reservation(): global reservations data = request.get_json() or {} required = ['name', 'email', 'date', 'time', 'guests', 'table_ids'] for field in required: if not data.get(field): return jsonify({'error': f'{field} is required'}), 400 if not re.match(r'^[^@]+@[^@]+\.[^@]+$', data['email']): return jsonify({'error': 'Invalid email address'}), 400 booking_id = generate_booking_id() table_names = [] for table_id in data['table_ids']: table, room = get_table(table_id) if table: table_names.append(f"{room['name']}/{table['name']}" if room else table['name']) reservation = { 'id': len(reservations) + 1, 'booking_id': booking_id, 'name': data['name'], 'email': data['email'], 'phone': data.get('phone', ''), 'date': data['date'], 'time': data['time'], 'guests': data['guests'], 'table_ids': data['table_ids'], 'table_names': ', '.join(table_names), 'notes': data.get('notes', ''), 'created_at': datetime.now().isoformat() } reservations.append(reservation) try: email_body = email_templates['confirmation'].format( name=data['name'], booking_id=booking_id, date=data['date'], time=data['time'], guests=data['guests'], tables=', '.join(table_names) ) send_email_smtp(data['email'], f'Ihre Reservierung - {booking_id}', email_body) except Exception as e: print(f"Email error: {e}") return jsonify({'status': 'ok', 'booking_id': booking_id}) # Admin API @app.route('/api/admin/reservations') def admin_get_reservations(): if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 return jsonify({'reservations': reservations}) @app.route('/api/admin/reservations/search') def admin_search_reservations(): if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 query = request.args.get('q', '').lower() if not query: return jsonify({'reservations': reservations}) results = [r for r in reservations if query in r.get('name', '').lower() or query in r.get('email', '').lower() or query in r.get('booking_id', '').lower()] return jsonify({'reservations': results}) # Users API @app.route('/api/admin/users') def admin_get_users(): if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 current = next((u for u in users if u['id'] == session['user_id']), None) if not current or current['role'] != 'admin': return jsonify({'error': 'Forbidden'}), 403 safe_users = [{'id': u['id'], 'username': u['username'], 'name': u['name'], 'email': u['email'], 'role': u['role'], 'is_active': u.get('is_active', True), 'last_login': u.get('last_login')} for u in users] return jsonify({'users': safe_users}) @app.route('/api/admin/users', methods=['POST']) def admin_create_user(): global users if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 current = next((u for u in users if u['id'] == session['user_id']), None) if not current or current['role'] != 'admin': return jsonify({'error': 'Forbidden'}), 403 data = request.get_json() or {} if not all(k in data for k in ['username', 'name', 'email', 'role']): return jsonify({'error': 'Missing required fields'}), 400 if any(u['username'].lower() == data['username'].lower() for u in users): return jsonify({'error': 'Username already exists'}), 400 temp_password = ''.join(random.choices(string.ascii_letters + string.digits, k=10)) new_user = { 'id': max([u['id'] for u in users], default=0) + 1, 'username': data['username'], 'password_hash': hash_password(temp_password), 'name': data['name'], 'email': data['email'], 'role': data['role'], 'is_active': True, 'force_password_change': True, # Muss Passwort bei erstem Login ändern 'created_at': datetime.now().isoformat(), 'last_login': None } users.append(new_user) if data.get('send_email', True): try: body = email_templates['user_welcome'].format( name=data['name'], username=data['username'], password=temp_password, role=data['role'] ) send_email_smtp(data['email'], 'Ihr Reservierungssystem-Zugang', body) except Exception as e: print(f"Welcome email error: {e}") return jsonify({'status': 'ok', 'temp_password': temp_password if not data.get('send_email') else None}) # Rooms, Templates, SMTP API @app.route('/api/rooms') def get_rooms(): if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 return jsonify({'rooms': rooms}) @app.route('/api/admin/templates', methods=['GET', 'POST']) def handle_templates(): global email_templates if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 if request.method == 'POST': data = request.get_json() or {} if 'confirmation' in data: email_templates['confirmation'] = data['confirmation'] if 'email_reply' in data: email_templates['email_reply'] = data['email_reply'] return jsonify({'status': 'ok'}) return jsonify({'templates': email_templates}) @app.route('/api/admin/smtp', methods=['GET', 'POST']) def handle_smtp(): global smtp_config if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 if request.method == 'POST': data = request.get_json() or {} smtp_config.update(data) return jsonify({'status': 'ok'}) return jsonify({ 'host': smtp_config.get('host', ''), 'port': smtp_config.get('port', 587), 'user': smtp_config.get('user', ''), 'from': smtp_config.get('from', '') }) @app.route('/api/admin/smtp/test', methods=['POST']) def test_smtp(): if 'user_id' not in session: return jsonify({'error': 'Unauthorized'}), 401 result = send_email_smtp( smtp_config.get('user', 'test@example.com'), 'SMTP Test', 'Dies ist eine Testnachricht.' ) if result: return jsonify({'status': 'ok'}) return jsonify({'error': 'SMTP test failed'}), 500 # Main routes @app.route('/') def index(): if 'user_id' in session: return redirect('/admin') return render_template_string(PUBLIC_RESERVATION_HTML, min_date=str(date.today())) @app.route('/admin') def admin(): if 'user_id' not in session: return render_template_string(ADMIN_LOGIN_HTML, default_username='admin', default_password='changeme', force_password_change=False) user = next((u for u in users if u['id'] == session['user_id']), None) if not user: session.pop('user_id', None) return redirect('/admin') return render_template_string(ADMIN_DASHBOARD_HTML, template_confirmation=email_templates['confirmation'], template_email_reply=email_templates['email_reply'], smtp_host=smtp_config.get('host', ''), smtp_port=smtp_config.get('port', 587), smtp_user=smtp_config.get('user', ''), smtp_password='***' if smtp_config.get('password') else '', smtp_from=smtp_config.get('from', '') ) @app.route('/change-password') def change_password_page(): if 'user_id' not in session: return redirect('/') user = next((u for u in users if u['id'] == session['user_id']), None) if not user: session.pop('user_id', None) return redirect('/') return render_template_string(CHANGE_PASSWORD_HTML, force_change=user.get('force_password_change', False)) if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)