Files
skyevouchers/app.py
T
2026-06-28 16:51:38 +00:00

187 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/<int:vid>', 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)