from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash import mysql.connector import os import re import requests import random import string from datetime import datetime from functools import wraps app = Flask(__name__) app.secret_key = os.environ.get('SECRET_KEY', 'skyevouchers-secret-key-2024') # Config DB_CONFIG = { 'host': os.environ.get('DB_HOST', 'localhost'), 'port': int(os.environ.get('DB_PORT', 3306)), 'user': os.environ.get('DB_USER', 'root'), 'password': os.environ.get('DB_PASSWORD', ''), 'database': os.environ.get('DB_NAME', 'skyevouchers'), } ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'admin123') GOTIFY_URL = os.environ.get('GOTIFY_URL', 'http://localhost:8080') GOTIFY_TOKEN = os.environ.get('GOTIFY_TOKEN', '') LOW_VOUCHER_THRESHOLD = 2 def get_db(): return mysql.connector.connect(**DB_CONFIG) def init_db(): conn = get_db() c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS vouchers ( id INT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(20) NOT NULL UNIQUE, used BOOLEAN DEFAULT FALSE, used_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )''') conn.commit() conn.close() def send_gotify_notification(remaining): if not GOTIFY_TOKEN: return try: requests.post( f"{GOTIFY_URL}/message", json={ "title": "⚠️ Skye Vouchers – Niedriger Bestand", "message": f"Nur noch {remaining} Voucher(s) verfügbar! Bitte neue Vouchers hinzufügen.", "priority": 8 }, params={"token": GOTIFY_TOKEN}, timeout=5 ) except Exception: pass def login_required(f): @wraps(f) def decorated(*args, **kwargs): if not session.get('admin_logged_in'): return redirect(url_for('admin_login')) return f(*args, **kwargs) return decorated # ── Frontend ────────────────────────────────────────────────────────────────── @app.route('/') def index(): return render_template('index.html') @app.route('/api/get-voucher', methods=['POST']) def get_voucher(): conn = get_db() c = conn.cursor(dictionary=True) c.execute('SELECT id, code FROM vouchers WHERE used = FALSE ORDER BY RAND() LIMIT 1') voucher = c.fetchone() if not voucher: conn.close() return jsonify({'error': 'Keine Vouchers mehr verfügbar'}), 404 c.execute('UPDATE vouchers SET used = TRUE, used_at = %s WHERE id = %s', (datetime.now(), voucher['id'])) conn.commit() # Check remaining c.execute('SELECT COUNT(*) AS cnt FROM vouchers WHERE used = FALSE') remaining = c.fetchone()['cnt'] conn.close() if remaining <= LOW_VOUCHER_THRESHOLD: send_gotify_notification(remaining) return jsonify({'code': voucher['code']}) # ── Admin ───────────────────────────────────────────────────────────────────── @app.route('/admin/login', methods=['GET', 'POST']) def admin_login(): if request.method == 'POST': if request.form.get('password') == ADMIN_PASSWORD: session['admin_logged_in'] = True return redirect(url_for('admin_dashboard')) flash('Falsches Passwort', 'error') return render_template('admin_login.html') @app.route('/admin/logout') def admin_logout(): session.clear() return redirect(url_for('admin_login')) @app.route('/admin') @login_required def admin_dashboard(): conn = get_db() c = conn.cursor(dictionary=True) c.execute('SELECT COUNT(*) AS cnt FROM vouchers WHERE used = FALSE') available = c.fetchone()['cnt'] c.execute('SELECT COUNT(*) AS cnt FROM vouchers WHERE used = TRUE') used_count = c.fetchone()['cnt'] c.execute('SELECT COUNT(*) AS cnt FROM vouchers') total = c.fetchone()['cnt'] c.execute('SELECT code, used_at FROM vouchers WHERE used = TRUE ORDER BY used_at DESC LIMIT 20') recent_used = c.fetchall() c.execute('SELECT code, created_at FROM vouchers WHERE used = FALSE ORDER BY created_at DESC LIMIT 20') available_vouchers = c.fetchall() conn.close() return render_template('admin_dashboard.html', available=available, used_count=used_count, total=total, recent_used=recent_used, available_vouchers=available_vouchers) @app.route('/admin/import', methods=['POST']) @login_required def admin_import(): text = request.form.get('voucher_text', '') # Extract codes matching xxxxx-xxxxx (5 digits, dash, 5 digits) codes = re.findall(r'\b(\d{5}-\d{5})\b', text) if not codes: flash('Keine gültigen Voucher-Codes gefunden.', 'error') return redirect(url_for('admin_dashboard')) conn = get_db() c = conn.cursor() imported = 0 skipped = 0 for code in codes: try: c.execute('INSERT INTO vouchers (code) VALUES (%s)', (code,)) imported += 1 except mysql.connector.IntegrityError: skipped += 1 conn.commit() conn.close() flash(f'{imported} Voucher(s) importiert, {skipped} bereits vorhanden.', 'success') return redirect(url_for('admin_dashboard')) @app.route('/admin/delete/', methods=['POST']) @login_required def admin_delete(vid): conn = get_db() c = conn.cursor() c.execute('DELETE FROM vouchers WHERE id = %s', (vid,)) conn.commit() conn.close() return jsonify({'ok': True}) if __name__ == '__main__': init_db() app.run(debug=True, host='0.0.0.0', port=5000)